feat: residues accounting
This commit is contained in:
@@ -42,8 +42,10 @@ export type { ClientCreateRequest } from './models/ClientCreateRequest';
|
|||||||
export type { ClientCreateResponse } from './models/ClientCreateResponse';
|
export type { ClientCreateResponse } from './models/ClientCreateResponse';
|
||||||
export type { ClientDeleteRequest } from './models/ClientDeleteRequest';
|
export type { ClientDeleteRequest } from './models/ClientDeleteRequest';
|
||||||
export type { ClientDeleteResponse } from './models/ClientDeleteResponse';
|
export type { ClientDeleteResponse } from './models/ClientDeleteResponse';
|
||||||
|
export type { ClientDetailedSchema } from './models/ClientDetailedSchema';
|
||||||
export type { ClientDetailsSchema } from './models/ClientDetailsSchema';
|
export type { ClientDetailsSchema } from './models/ClientDetailsSchema';
|
||||||
export type { ClientGetAllResponse } from './models/ClientGetAllResponse';
|
export type { ClientGetAllResponse } from './models/ClientGetAllResponse';
|
||||||
|
export type { ClientGetResponse } from './models/ClientGetResponse';
|
||||||
export type { ClientSchema } from './models/ClientSchema';
|
export type { ClientSchema } from './models/ClientSchema';
|
||||||
export type { ClientUpdateDetailsRequest } from './models/ClientUpdateDetailsRequest';
|
export type { ClientUpdateDetailsRequest } from './models/ClientUpdateDetailsRequest';
|
||||||
export type { ClientUpdateRequest } from './models/ClientUpdateRequest';
|
export type { ClientUpdateRequest } from './models/ClientUpdateRequest';
|
||||||
@@ -71,6 +73,13 @@ export type { CreatePositionRequest } from './models/CreatePositionRequest';
|
|||||||
export type { CreatePositionResponse } from './models/CreatePositionResponse';
|
export type { CreatePositionResponse } from './models/CreatePositionResponse';
|
||||||
export type { CreatePriceCategoryRequest } from './models/CreatePriceCategoryRequest';
|
export type { CreatePriceCategoryRequest } from './models/CreatePriceCategoryRequest';
|
||||||
export type { CreatePriceCategoryResponse } from './models/CreatePriceCategoryResponse';
|
export type { CreatePriceCategoryResponse } from './models/CreatePriceCategoryResponse';
|
||||||
|
export type { CreateResidualBoxRequest } from './models/CreateResidualBoxRequest';
|
||||||
|
export type { CreateResidualBoxResponse } from './models/CreateResidualBoxResponse';
|
||||||
|
export type { CreateResidualPalletRequest } from './models/CreateResidualPalletRequest';
|
||||||
|
export type { CreateResidualPalletResponse } from './models/CreateResidualPalletResponse';
|
||||||
|
export type { CreateResidualProductRequest } from './models/CreateResidualProductRequest';
|
||||||
|
export type { CreateResidualProductResponse } from './models/CreateResidualProductResponse';
|
||||||
|
export type { CreateResidualProductSchema } from './models/CreateResidualProductSchema';
|
||||||
export type { CreateServiceKitSchema } from './models/CreateServiceKitSchema';
|
export type { CreateServiceKitSchema } from './models/CreateServiceKitSchema';
|
||||||
export type { CreateServicesKitRequest } from './models/CreateServicesKitRequest';
|
export type { CreateServicesKitRequest } from './models/CreateServicesKitRequest';
|
||||||
export type { CreateServicesKitResponse } from './models/CreateServicesKitResponse';
|
export type { CreateServicesKitResponse } from './models/CreateServicesKitResponse';
|
||||||
@@ -163,6 +172,9 @@ export type { DeletePositionRequest } from './models/DeletePositionRequest';
|
|||||||
export type { DeletePositionResponse } from './models/DeletePositionResponse';
|
export type { DeletePositionResponse } from './models/DeletePositionResponse';
|
||||||
export type { DeletePriceCategoryRequest } from './models/DeletePriceCategoryRequest';
|
export type { DeletePriceCategoryRequest } from './models/DeletePriceCategoryRequest';
|
||||||
export type { DeletePriceCategoryResponse } from './models/DeletePriceCategoryResponse';
|
export type { DeletePriceCategoryResponse } from './models/DeletePriceCategoryResponse';
|
||||||
|
export type { DeleteResidualBoxResponse } from './models/DeleteResidualBoxResponse';
|
||||||
|
export type { DeleteResidualPalletResponse } from './models/DeleteResidualPalletResponse';
|
||||||
|
export type { DeleteResidualProductResponse } from './models/DeleteResidualProductResponse';
|
||||||
export type { DeleteShiftResponse } from './models/DeleteShiftResponse';
|
export type { DeleteShiftResponse } from './models/DeleteShiftResponse';
|
||||||
export type { DeleteShippingProductResponse } from './models/DeleteShippingProductResponse';
|
export type { DeleteShippingProductResponse } from './models/DeleteShippingProductResponse';
|
||||||
export type { DeleteShippingWarehouseRequest } from './models/DeleteShippingWarehouseRequest';
|
export type { DeleteShippingWarehouseRequest } from './models/DeleteShippingWarehouseRequest';
|
||||||
@@ -214,6 +226,8 @@ export type { GetProfitChartDataRequest } from './models/GetProfitChartDataReque
|
|||||||
export type { GetProfitChartDataResponse } from './models/GetProfitChartDataResponse';
|
export type { GetProfitChartDataResponse } from './models/GetProfitChartDataResponse';
|
||||||
export type { GetProfitTableDataRequest } from './models/GetProfitTableDataRequest';
|
export type { GetProfitTableDataRequest } from './models/GetProfitTableDataRequest';
|
||||||
export type { GetProfitTableDataResponse } from './models/GetProfitTableDataResponse';
|
export type { GetProfitTableDataResponse } from './models/GetProfitTableDataResponse';
|
||||||
|
export type { GetResidualBoxResponse } from './models/GetResidualBoxResponse';
|
||||||
|
export type { GetResidualPalletResponse } from './models/GetResidualPalletResponse';
|
||||||
export type { GetServiceKitSchema } from './models/GetServiceKitSchema';
|
export type { GetServiceKitSchema } from './models/GetServiceKitSchema';
|
||||||
export type { GetTimeTrackingRecordsRequest } from './models/GetTimeTrackingRecordsRequest';
|
export type { GetTimeTrackingRecordsRequest } from './models/GetTimeTrackingRecordsRequest';
|
||||||
export type { GetTimeTrackingRecordsResponse } from './models/GetTimeTrackingRecordsResponse';
|
export type { GetTimeTrackingRecordsResponse } from './models/GetTimeTrackingRecordsResponse';
|
||||||
@@ -221,6 +235,8 @@ export type { GetTransactionTagsResponse } from './models/GetTransactionTagsResp
|
|||||||
export type { GetWorkShiftsResponse } from './models/GetWorkShiftsResponse';
|
export type { GetWorkShiftsResponse } from './models/GetWorkShiftsResponse';
|
||||||
export type { GroupBillRequestSchema } from './models/GroupBillRequestSchema';
|
export type { GroupBillRequestSchema } from './models/GroupBillRequestSchema';
|
||||||
export type { HTTPValidationError } from './models/HTTPValidationError';
|
export type { HTTPValidationError } from './models/HTTPValidationError';
|
||||||
|
export type { LoadReceiptRequest } from './models/LoadReceiptRequest';
|
||||||
|
export type { LoadReceiptResponse } from './models/LoadReceiptResponse';
|
||||||
export type { ManageEmployeeRequest } from './models/ManageEmployeeRequest';
|
export type { ManageEmployeeRequest } from './models/ManageEmployeeRequest';
|
||||||
export type { ManageEmployeeResponse } from './models/ManageEmployeeResponse';
|
export type { ManageEmployeeResponse } from './models/ManageEmployeeResponse';
|
||||||
export type { MarketplaceCreateSchema } from './models/MarketplaceCreateSchema';
|
export type { MarketplaceCreateSchema } from './models/MarketplaceCreateSchema';
|
||||||
@@ -242,6 +258,7 @@ export type { PermissionSchema } from './models/PermissionSchema';
|
|||||||
export type { PositionSchema } from './models/PositionSchema';
|
export type { PositionSchema } from './models/PositionSchema';
|
||||||
export type { ProductAddBarcodeRequest } from './models/ProductAddBarcodeRequest';
|
export type { ProductAddBarcodeRequest } from './models/ProductAddBarcodeRequest';
|
||||||
export type { ProductAddBarcodeResponse } from './models/ProductAddBarcodeResponse';
|
export type { ProductAddBarcodeResponse } from './models/ProductAddBarcodeResponse';
|
||||||
|
export type { ProductAndQuantitySchema } from './models/ProductAndQuantitySchema';
|
||||||
export type { ProductCreateRequest } from './models/ProductCreateRequest';
|
export type { ProductCreateRequest } from './models/ProductCreateRequest';
|
||||||
export type { ProductCreateResponse } from './models/ProductCreateResponse';
|
export type { ProductCreateResponse } from './models/ProductCreateResponse';
|
||||||
export type { ProductDeleteBarcodeImageResponse } from './models/ProductDeleteBarcodeImageResponse';
|
export type { ProductDeleteBarcodeImageResponse } from './models/ProductDeleteBarcodeImageResponse';
|
||||||
@@ -262,6 +279,11 @@ export type { ProductUploadImageResponse } from './models/ProductUploadImageResp
|
|||||||
export type { ProfitChartDataItem } from './models/ProfitChartDataItem';
|
export type { ProfitChartDataItem } from './models/ProfitChartDataItem';
|
||||||
export type { ProfitTableDataItem } from './models/ProfitTableDataItem';
|
export type { ProfitTableDataItem } from './models/ProfitTableDataItem';
|
||||||
export type { ProfitTableGroupBy } from './models/ProfitTableGroupBy';
|
export type { ProfitTableGroupBy } from './models/ProfitTableGroupBy';
|
||||||
|
export type { ReceiptBoxSchema } from './models/ReceiptBoxSchema';
|
||||||
|
export type { ReceiptPalletSchema } from './models/ReceiptPalletSchema';
|
||||||
|
export type { ResidualBoxSchema } from './models/ResidualBoxSchema';
|
||||||
|
export type { ResidualPalletSchema } from './models/ResidualPalletSchema';
|
||||||
|
export type { ResidualProductSchema } from './models/ResidualProductSchema';
|
||||||
export type { RoleSchema } from './models/RoleSchema';
|
export type { RoleSchema } from './models/RoleSchema';
|
||||||
export type { ServiceCategoryPriceSchema } from './models/ServiceCategoryPriceSchema';
|
export type { ServiceCategoryPriceSchema } from './models/ServiceCategoryPriceSchema';
|
||||||
export type { ServiceCategoryReorderRequest } from './models/ServiceCategoryReorderRequest';
|
export type { ServiceCategoryReorderRequest } from './models/ServiceCategoryReorderRequest';
|
||||||
@@ -310,6 +332,9 @@ export type { UpdatePayRateRequest } from './models/UpdatePayRateRequest';
|
|||||||
export type { UpdatePayRateResponse } from './models/UpdatePayRateResponse';
|
export type { UpdatePayRateResponse } from './models/UpdatePayRateResponse';
|
||||||
export type { UpdatePriceCategoryRequest } from './models/UpdatePriceCategoryRequest';
|
export type { UpdatePriceCategoryRequest } from './models/UpdatePriceCategoryRequest';
|
||||||
export type { UpdatePriceCategoryResponse } from './models/UpdatePriceCategoryResponse';
|
export type { UpdatePriceCategoryResponse } from './models/UpdatePriceCategoryResponse';
|
||||||
|
export type { UpdateResidualProductRequest } from './models/UpdateResidualProductRequest';
|
||||||
|
export type { UpdateResidualProductResponse } from './models/UpdateResidualProductResponse';
|
||||||
|
export type { UpdateResidualProductSchema } from './models/UpdateResidualProductSchema';
|
||||||
export type { UpdateServiceKitSchema } from './models/UpdateServiceKitSchema';
|
export type { UpdateServiceKitSchema } from './models/UpdateServiceKitSchema';
|
||||||
export type { UpdateServicesKitRequest } from './models/UpdateServicesKitRequest';
|
export type { UpdateServicesKitRequest } from './models/UpdateServicesKitRequest';
|
||||||
export type { UpdateServicesKitResponse } from './models/UpdateServicesKitResponse';
|
export type { UpdateServicesKitResponse } from './models/UpdateServicesKitResponse';
|
||||||
@@ -345,6 +370,7 @@ export { MarketplaceService } from './services/MarketplaceService';
|
|||||||
export { PayrollService } from './services/PayrollService';
|
export { PayrollService } from './services/PayrollService';
|
||||||
export { PositionService } from './services/PositionService';
|
export { PositionService } from './services/PositionService';
|
||||||
export { ProductService } from './services/ProductService';
|
export { ProductService } from './services/ProductService';
|
||||||
|
export { ResiduesService } from './services/ResiduesService';
|
||||||
export { RoleService } from './services/RoleService';
|
export { RoleService } from './services/RoleService';
|
||||||
export { ServiceService } from './services/ServiceService';
|
export { ServiceService } from './services/ServiceService';
|
||||||
export { ShippingService } from './services/ShippingService';
|
export { ShippingService } from './services/ShippingService';
|
||||||
|
|||||||
19
src/client/models/ClientDetailedSchema.ts
Normal file
19
src/client/models/ClientDetailedSchema.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { BarcodeTemplateSchema } from './BarcodeTemplateSchema';
|
||||||
|
import type { ClientDetailsSchema } from './ClientDetailsSchema';
|
||||||
|
import type { ResidualBoxSchema } from './ResidualBoxSchema';
|
||||||
|
import type { ResidualPalletSchema } from './ResidualPalletSchema';
|
||||||
|
export type ClientDetailedSchema = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
companyName: string;
|
||||||
|
barcodeTemplate?: (BarcodeTemplateSchema | null);
|
||||||
|
comment?: (string | null);
|
||||||
|
details?: (ClientDetailsSchema | null);
|
||||||
|
pallets?: Array<ResidualPalletSchema>;
|
||||||
|
boxes?: Array<ResidualBoxSchema>;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/ClientGetResponse.ts
Normal file
9
src/client/models/ClientGetResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { ClientDetailedSchema } from './ClientDetailedSchema';
|
||||||
|
export type ClientGetResponse = {
|
||||||
|
client: ClientDetailedSchema;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/CreateResidualBoxRequest.ts
Normal file
9
src/client/models/CreateResidualBoxRequest.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type CreateResidualBoxRequest = {
|
||||||
|
clientId: (number | null);
|
||||||
|
palletId: (number | null);
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/CreateResidualBoxResponse.ts
Normal file
9
src/client/models/CreateResidualBoxResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type CreateResidualBoxResponse = {
|
||||||
|
ok: boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
8
src/client/models/CreateResidualPalletRequest.ts
Normal file
8
src/client/models/CreateResidualPalletRequest.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type CreateResidualPalletRequest = {
|
||||||
|
clientId: number;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/CreateResidualPalletResponse.ts
Normal file
9
src/client/models/CreateResidualPalletResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type CreateResidualPalletResponse = {
|
||||||
|
ok: boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/CreateResidualProductRequest.ts
Normal file
9
src/client/models/CreateResidualProductRequest.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { CreateResidualProductSchema } from './CreateResidualProductSchema';
|
||||||
|
export type CreateResidualProductRequest = {
|
||||||
|
data: CreateResidualProductSchema;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/CreateResidualProductResponse.ts
Normal file
9
src/client/models/CreateResidualProductResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type CreateResidualProductResponse = {
|
||||||
|
ok: boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
11
src/client/models/CreateResidualProductSchema.ts
Normal file
11
src/client/models/CreateResidualProductSchema.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type CreateResidualProductSchema = {
|
||||||
|
productId: (number | null);
|
||||||
|
quantity: (number | null);
|
||||||
|
palletId: (number | null);
|
||||||
|
boxId: (number | null);
|
||||||
|
};
|
||||||
|
|
||||||
@@ -7,6 +7,7 @@ export type DealGeneralInfoSchema = {
|
|||||||
name: string;
|
name: string;
|
||||||
isDeleted: boolean;
|
isDeleted: boolean;
|
||||||
isCompleted: boolean;
|
isCompleted: boolean;
|
||||||
|
isAccounted: boolean;
|
||||||
comment: string;
|
comment: string;
|
||||||
shippingWarehouse?: (string | null);
|
shippingWarehouse?: (string | null);
|
||||||
deliveryDate?: (string | null);
|
deliveryDate?: (string | null);
|
||||||
|
|||||||
9
src/client/models/DeleteResidualBoxResponse.ts
Normal file
9
src/client/models/DeleteResidualBoxResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type DeleteResidualBoxResponse = {
|
||||||
|
ok: boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/DeleteResidualPalletResponse.ts
Normal file
9
src/client/models/DeleteResidualPalletResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type DeleteResidualPalletResponse = {
|
||||||
|
ok: boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/DeleteResidualProductResponse.ts
Normal file
9
src/client/models/DeleteResidualProductResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type DeleteResidualProductResponse = {
|
||||||
|
ok: boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
10
src/client/models/GetResidualBoxResponse.ts
Normal file
10
src/client/models/GetResidualBoxResponse.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { ResidualBoxSchema } from './ResidualBoxSchema';
|
||||||
|
export type GetResidualBoxResponse = {
|
||||||
|
box: ResidualBoxSchema;
|
||||||
|
clientId: number;
|
||||||
|
};
|
||||||
|
|
||||||
10
src/client/models/GetResidualPalletResponse.ts
Normal file
10
src/client/models/GetResidualPalletResponse.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { ResidualPalletSchema } from './ResidualPalletSchema';
|
||||||
|
export type GetResidualPalletResponse = {
|
||||||
|
pallet: ResidualPalletSchema;
|
||||||
|
clientId: number;
|
||||||
|
};
|
||||||
|
|
||||||
12
src/client/models/LoadReceiptRequest.ts
Normal file
12
src/client/models/LoadReceiptRequest.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { ReceiptBoxSchema } from './ReceiptBoxSchema';
|
||||||
|
import type { ReceiptPalletSchema } from './ReceiptPalletSchema';
|
||||||
|
export type LoadReceiptRequest = {
|
||||||
|
pallets: Array<ReceiptPalletSchema>;
|
||||||
|
boxes: Array<ReceiptBoxSchema>;
|
||||||
|
clientId: number;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/LoadReceiptResponse.ts
Normal file
9
src/client/models/LoadReceiptResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type LoadReceiptResponse = {
|
||||||
|
ok: boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/ProductAndQuantitySchema.ts
Normal file
9
src/client/models/ProductAndQuantitySchema.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type ProductAndQuantitySchema = {
|
||||||
|
productId: (number | null);
|
||||||
|
quantity: (number | null);
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/ReceiptBoxSchema.ts
Normal file
9
src/client/models/ReceiptBoxSchema.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { ProductAndQuantitySchema } from './ProductAndQuantitySchema';
|
||||||
|
export type ReceiptBoxSchema = {
|
||||||
|
products: Array<ProductAndQuantitySchema>;
|
||||||
|
};
|
||||||
|
|
||||||
11
src/client/models/ReceiptPalletSchema.ts
Normal file
11
src/client/models/ReceiptPalletSchema.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { ProductAndQuantitySchema } from './ProductAndQuantitySchema';
|
||||||
|
import type { ReceiptBoxSchema } from './ReceiptBoxSchema';
|
||||||
|
export type ReceiptPalletSchema = {
|
||||||
|
products: Array<ProductAndQuantitySchema>;
|
||||||
|
boxes: Array<ReceiptBoxSchema>;
|
||||||
|
};
|
||||||
|
|
||||||
13
src/client/models/ResidualBoxSchema.ts
Normal file
13
src/client/models/ResidualBoxSchema.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { ResidualProductSchema } from './ResidualProductSchema';
|
||||||
|
export type ResidualBoxSchema = {
|
||||||
|
id: number;
|
||||||
|
createdAt: string;
|
||||||
|
palletId: (number | null);
|
||||||
|
clientId: (number | null);
|
||||||
|
residualProducts: Array<ResidualProductSchema>;
|
||||||
|
};
|
||||||
|
|
||||||
13
src/client/models/ResidualPalletSchema.ts
Normal file
13
src/client/models/ResidualPalletSchema.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { ResidualBoxSchema } from './ResidualBoxSchema';
|
||||||
|
import type { ResidualProductSchema } from './ResidualProductSchema';
|
||||||
|
export type ResidualPalletSchema = {
|
||||||
|
id: number;
|
||||||
|
createdAt: string;
|
||||||
|
boxes: Array<ResidualBoxSchema>;
|
||||||
|
residualProducts: Array<ResidualProductSchema>;
|
||||||
|
};
|
||||||
|
|
||||||
13
src/client/models/ResidualProductSchema.ts
Normal file
13
src/client/models/ResidualProductSchema.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { ProductSchema } from './ProductSchema';
|
||||||
|
export type ResidualProductSchema = {
|
||||||
|
id: number;
|
||||||
|
quantity: number;
|
||||||
|
product: ProductSchema;
|
||||||
|
palletId: (number | null);
|
||||||
|
boxId: (number | null);
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/UpdateResidualProductRequest.ts
Normal file
9
src/client/models/UpdateResidualProductRequest.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { UpdateResidualProductSchema } from './UpdateResidualProductSchema';
|
||||||
|
export type UpdateResidualProductRequest = {
|
||||||
|
data: UpdateResidualProductSchema;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/UpdateResidualProductResponse.ts
Normal file
9
src/client/models/UpdateResidualProductResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type UpdateResidualProductResponse = {
|
||||||
|
ok: boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/UpdateResidualProductSchema.ts
Normal file
9
src/client/models/UpdateResidualProductSchema.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type UpdateResidualProductSchema = {
|
||||||
|
productId: (number | null);
|
||||||
|
quantity: (number | null);
|
||||||
|
};
|
||||||
|
|
||||||
@@ -7,6 +7,7 @@ import type { ClientCreateResponse } from '../models/ClientCreateResponse';
|
|||||||
import type { ClientDeleteRequest } from '../models/ClientDeleteRequest';
|
import type { ClientDeleteRequest } from '../models/ClientDeleteRequest';
|
||||||
import type { ClientDeleteResponse } from '../models/ClientDeleteResponse';
|
import type { ClientDeleteResponse } from '../models/ClientDeleteResponse';
|
||||||
import type { ClientGetAllResponse } from '../models/ClientGetAllResponse';
|
import type { ClientGetAllResponse } from '../models/ClientGetAllResponse';
|
||||||
|
import type { ClientGetResponse } from '../models/ClientGetResponse';
|
||||||
import type { ClientUpdateDetailsRequest } from '../models/ClientUpdateDetailsRequest';
|
import type { ClientUpdateDetailsRequest } from '../models/ClientUpdateDetailsRequest';
|
||||||
import type { ClientUpdateRequest } from '../models/ClientUpdateRequest';
|
import type { ClientUpdateRequest } from '../models/ClientUpdateRequest';
|
||||||
import type { ClientUpdateResponse } from '../models/ClientUpdateResponse';
|
import type { ClientUpdateResponse } from '../models/ClientUpdateResponse';
|
||||||
@@ -66,6 +67,27 @@ export class ClientService {
|
|||||||
url: '/client/get-all',
|
url: '/client/get-all',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Get Client
|
||||||
|
* @returns ClientGetResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static getClient({
|
||||||
|
clientId,
|
||||||
|
}: {
|
||||||
|
clientId: number,
|
||||||
|
}): CancelablePromise<ClientGetResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/client/get/{client_id}',
|
||||||
|
path: {
|
||||||
|
'client_id': clientId,
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Create Client
|
* Create Client
|
||||||
* @returns ClientCreateResponse Successful Response
|
* @returns ClientCreateResponse Successful Response
|
||||||
|
|||||||
258
src/client/services/ResiduesService.ts
Normal file
258
src/client/services/ResiduesService.ts
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { CreateResidualBoxRequest } from '../models/CreateResidualBoxRequest';
|
||||||
|
import type { CreateResidualBoxResponse } from '../models/CreateResidualBoxResponse';
|
||||||
|
import type { CreateResidualPalletRequest } from '../models/CreateResidualPalletRequest';
|
||||||
|
import type { CreateResidualPalletResponse } from '../models/CreateResidualPalletResponse';
|
||||||
|
import type { CreateResidualProductRequest } from '../models/CreateResidualProductRequest';
|
||||||
|
import type { CreateResidualProductResponse } from '../models/CreateResidualProductResponse';
|
||||||
|
import type { DeleteResidualBoxResponse } from '../models/DeleteResidualBoxResponse';
|
||||||
|
import type { DeleteResidualPalletResponse } from '../models/DeleteResidualPalletResponse';
|
||||||
|
import type { DeleteResidualProductResponse } from '../models/DeleteResidualProductResponse';
|
||||||
|
import type { GetResidualBoxResponse } from '../models/GetResidualBoxResponse';
|
||||||
|
import type { GetResidualPalletResponse } from '../models/GetResidualPalletResponse';
|
||||||
|
import type { LoadReceiptRequest } from '../models/LoadReceiptRequest';
|
||||||
|
import type { LoadReceiptResponse } from '../models/LoadReceiptResponse';
|
||||||
|
import type { UpdateResidualProductRequest } from '../models/UpdateResidualProductRequest';
|
||||||
|
import type { UpdateResidualProductResponse } from '../models/UpdateResidualProductResponse';
|
||||||
|
import type { CancelablePromise } from '../core/CancelablePromise';
|
||||||
|
import { OpenAPI } from '../core/OpenAPI';
|
||||||
|
import { request as __request } from '../core/request';
|
||||||
|
export class ResiduesService {
|
||||||
|
/**
|
||||||
|
* Get Pallet
|
||||||
|
* @returns GetResidualPalletResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static getResidualPallet({
|
||||||
|
palletId,
|
||||||
|
}: {
|
||||||
|
palletId: number,
|
||||||
|
}): CancelablePromise<GetResidualPalletResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/residues/pallet/{pallet_id}',
|
||||||
|
path: {
|
||||||
|
'pallet_id': palletId,
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Delete Pallet
|
||||||
|
* @returns DeleteResidualPalletResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static deleteResidualPallet({
|
||||||
|
palletId,
|
||||||
|
}: {
|
||||||
|
palletId: number,
|
||||||
|
}): CancelablePromise<DeleteResidualPalletResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'DELETE',
|
||||||
|
url: '/residues/pallet/{pallet_id}',
|
||||||
|
path: {
|
||||||
|
'pallet_id': palletId,
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Create Pallet
|
||||||
|
* @returns CreateResidualPalletResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static createResidualPallet({
|
||||||
|
requestBody,
|
||||||
|
}: {
|
||||||
|
requestBody: CreateResidualPalletRequest,
|
||||||
|
}): CancelablePromise<CreateResidualPalletResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/residues/pallet',
|
||||||
|
body: requestBody,
|
||||||
|
mediaType: 'application/json',
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Create Residual Product
|
||||||
|
* @returns CreateResidualProductResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static createResidualProduct({
|
||||||
|
requestBody,
|
||||||
|
}: {
|
||||||
|
requestBody: CreateResidualProductRequest,
|
||||||
|
}): CancelablePromise<CreateResidualProductResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/residues/product',
|
||||||
|
body: requestBody,
|
||||||
|
mediaType: 'application/json',
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Update Residual Product
|
||||||
|
* @returns UpdateResidualProductResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static updateResidualProduct({
|
||||||
|
residualProductId,
|
||||||
|
requestBody,
|
||||||
|
}: {
|
||||||
|
residualProductId: number,
|
||||||
|
requestBody: UpdateResidualProductRequest,
|
||||||
|
}): CancelablePromise<UpdateResidualProductResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/residues/product/{residual_product_id}',
|
||||||
|
path: {
|
||||||
|
'residual_product_id': residualProductId,
|
||||||
|
},
|
||||||
|
body: requestBody,
|
||||||
|
mediaType: 'application/json',
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Delete Residual Product
|
||||||
|
* @returns DeleteResidualProductResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static deleteResidualProduct({
|
||||||
|
residualProductId,
|
||||||
|
}: {
|
||||||
|
residualProductId: number,
|
||||||
|
}): CancelablePromise<DeleteResidualProductResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'DELETE',
|
||||||
|
url: '/residues/product/{residual_product_id}',
|
||||||
|
path: {
|
||||||
|
'residual_product_id': residualProductId,
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get Box
|
||||||
|
* @returns GetResidualBoxResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static getResidualBox({
|
||||||
|
boxId,
|
||||||
|
}: {
|
||||||
|
boxId: number,
|
||||||
|
}): CancelablePromise<GetResidualBoxResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/residues/box/{box_id}',
|
||||||
|
path: {
|
||||||
|
'box_id': boxId,
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Delete Box
|
||||||
|
* @returns DeleteResidualBoxResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static deleteResidualBox({
|
||||||
|
boxId,
|
||||||
|
}: {
|
||||||
|
boxId: number,
|
||||||
|
}): CancelablePromise<DeleteResidualBoxResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'DELETE',
|
||||||
|
url: '/residues/box/{box_id}',
|
||||||
|
path: {
|
||||||
|
'box_id': boxId,
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Create Box
|
||||||
|
* @returns CreateResidualBoxResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static createResidualBox({
|
||||||
|
requestBody,
|
||||||
|
}: {
|
||||||
|
requestBody: CreateResidualBoxRequest,
|
||||||
|
}): CancelablePromise<CreateResidualBoxResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/residues/box',
|
||||||
|
body: requestBody,
|
||||||
|
mediaType: 'application/json',
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Receipt
|
||||||
|
* @returns LoadReceiptResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static receipt({
|
||||||
|
requestBody,
|
||||||
|
}: {
|
||||||
|
requestBody: LoadReceiptRequest,
|
||||||
|
}): CancelablePromise<LoadReceiptResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/residues/receipt',
|
||||||
|
body: requestBody,
|
||||||
|
mediaType: 'application/json',
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Generate Pdf
|
||||||
|
* @returns any Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static getPdf({
|
||||||
|
palletIds = '',
|
||||||
|
boxIds = '',
|
||||||
|
}: {
|
||||||
|
palletIds?: string,
|
||||||
|
boxIds?: string,
|
||||||
|
}): CancelablePromise<any> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/residues/pdf/',
|
||||||
|
query: {
|
||||||
|
'pallet_ids': palletIds,
|
||||||
|
'box_ids': boxIds,
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/components/InlineButton/InlineButton.tsx
Normal file
23
src/components/InlineButton/InlineButton.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { ReactNode } from "react";
|
||||||
|
import { Button, ButtonProps, Group } from "@mantine/core";
|
||||||
|
|
||||||
|
interface Props extends ButtonProps {
|
||||||
|
children?: ReactNode;
|
||||||
|
onClick?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const InlineButton = ({ children, onClick, ...props }: Props) => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="default"
|
||||||
|
onClick={onClick}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<Group gap="sm">
|
||||||
|
{children}
|
||||||
|
</Group>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InlineButton;
|
||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
IconCash, IconChartDots,
|
IconCash, IconChartDots,
|
||||||
IconDashboard,
|
IconDashboard,
|
||||||
IconFileBarcode,
|
IconFileBarcode,
|
||||||
IconHome2,
|
IconHome2, IconHomeEdit,
|
||||||
IconLogout,
|
IconLogout,
|
||||||
IconMan,
|
IconMan,
|
||||||
IconMoon,
|
IconMoon,
|
||||||
@@ -94,6 +94,11 @@ const mockdata = [
|
|||||||
label: "Маркетплейсы",
|
label: "Маркетплейсы",
|
||||||
href: "/marketplaces",
|
href: "/marketplaces",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
icon: IconHomeEdit,
|
||||||
|
label: "Остатки",
|
||||||
|
href: "/residues",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const adminMockdata = [
|
const adminMockdata = [
|
||||||
|
|||||||
@@ -27,3 +27,7 @@
|
|||||||
.container-full-height-fixed {
|
.container-full-height-fixed {
|
||||||
height: calc(100vh - (rem(20) * 2));
|
height: calc(100vh - (rem(20) * 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.container-no-border-radius {
|
||||||
|
border-radius: 0 !important;
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ type Props = {
|
|||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
fullHeight?: boolean;
|
fullHeight?: boolean;
|
||||||
fullHeightFixed?: boolean;
|
fullHeightFixed?: boolean;
|
||||||
|
noBorderRadius?: boolean;
|
||||||
};
|
};
|
||||||
export const PageBlock: FC<Props> = ({
|
export const PageBlock: FC<Props> = ({
|
||||||
children,
|
children,
|
||||||
@@ -15,6 +16,7 @@ export const PageBlock: FC<Props> = ({
|
|||||||
fluid = true,
|
fluid = true,
|
||||||
fullHeight = false,
|
fullHeight = false,
|
||||||
fullHeightFixed = false,
|
fullHeightFixed = false,
|
||||||
|
noBorderRadius = false,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -23,7 +25,8 @@ export const PageBlock: FC<Props> = ({
|
|||||||
styles["container"],
|
styles["container"],
|
||||||
fluid && styles["container-fluid"],
|
fluid && styles["container-fluid"],
|
||||||
fullHeight && styles["container-full-height"],
|
fullHeight && styles["container-full-height"],
|
||||||
fullHeightFixed && styles["container-full-height-fixed"]
|
fullHeightFixed && styles["container-full-height-fixed"],
|
||||||
|
noBorderRadius && styles["container-no-border-radius"]
|
||||||
)}>
|
)}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import AddDealServiceModal from "../pages/LeadsPage/modals/AddDealServiceModal.t
|
|||||||
import AddDealProductModal from "../pages/LeadsPage/modals/AddDealProductModal.tsx";
|
import AddDealProductModal from "../pages/LeadsPage/modals/AddDealProductModal.tsx";
|
||||||
import PrintBarcodeModal from "./PrintBarcodeModal/PrintBarcodeModal.tsx";
|
import PrintBarcodeModal from "./PrintBarcodeModal/PrintBarcodeModal.tsx";
|
||||||
import AddBarcodeModal from "./AddBarcodeModal/AddBarcodeModal.tsx";
|
import AddBarcodeModal from "./AddBarcodeModal/AddBarcodeModal.tsx";
|
||||||
import BarcodeTemplateFormModal from "../pages/BarcodePage/modals/BarcodeTemplateFormModal/BarcodeTemplateFormModal.tsx";
|
import BarcodeTemplateFormModal
|
||||||
|
from "../pages/BarcodePage/modals/BarcodeTemplateFormModal/BarcodeTemplateFormModal.tsx";
|
||||||
import ProductServiceFormModal from "../pages/LeadsPage/modals/ProductServiceFormModal.tsx";
|
import ProductServiceFormModal from "../pages/LeadsPage/modals/ProductServiceFormModal.tsx";
|
||||||
import UserFormModal from "../pages/AdminPage/modals/UserFormModal/UserFormModal.tsx";
|
import UserFormModal from "../pages/AdminPage/modals/UserFormModal/UserFormModal.tsx";
|
||||||
import EmployeeSelectModal from "./EmployeeSelectModal/EmployeeSelectModal.tsx";
|
import EmployeeSelectModal from "./EmployeeSelectModal/EmployeeSelectModal.tsx";
|
||||||
@@ -29,6 +30,9 @@ import DepartmentModal from "../pages/AdminPage/tabs/OrganizationalStructureTab/
|
|||||||
import AddUserToDepartmentModal
|
import AddUserToDepartmentModal
|
||||||
from "../pages/AdminPage/tabs/OrganizationalStructureTab/modals/AddUserToDepartmentModal.tsx";
|
from "../pages/AdminPage/tabs/OrganizationalStructureTab/modals/AddUserToDepartmentModal.tsx";
|
||||||
import AssignUserModal from "../pages/LeadsPage/tabs/EmployeesTab/modals/AssignUserModal.tsx";
|
import AssignUserModal from "../pages/LeadsPage/tabs/EmployeesTab/modals/AssignUserModal.tsx";
|
||||||
|
import ResidualProductModal from "../pages/ResiduesPage/modals/ResidualProductModal/ResidualProductModal.tsx";
|
||||||
|
import NewReceiptModal from "../pages/ReceiptPage/components/NewReceipt/modals/NewReceiptModal.tsx";
|
||||||
|
import ReceiptModal from "../pages/ReceiptPage/components/ReceiptEditing/modals/ReceiptModal.tsx";
|
||||||
|
|
||||||
export const modals = {
|
export const modals = {
|
||||||
enterDeadline: EnterDeadlineModal,
|
enterDeadline: EnterDeadlineModal,
|
||||||
@@ -58,6 +62,9 @@ export const modals = {
|
|||||||
transactionFormModal: TransactionFormModal,
|
transactionFormModal: TransactionFormModal,
|
||||||
transactionTagsModal: TransactionTagsModal,
|
transactionTagsModal: TransactionTagsModal,
|
||||||
shippingProductModal: ShippingProductModal,
|
shippingProductModal: ShippingProductModal,
|
||||||
|
residualProductModal: ResidualProductModal,
|
||||||
|
newReceiptModal: NewReceiptModal,
|
||||||
|
receiptModal: ReceiptModal,
|
||||||
departmentModal: DepartmentModal,
|
departmentModal: DepartmentModal,
|
||||||
addUserToDepartmentModal: AddUserToDepartmentModal,
|
addUserToDepartmentModal: AddUserToDepartmentModal,
|
||||||
assignUserModal: AssignUserModal,
|
assignUserModal: AssignUserModal,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import ShippingTree from "./components/ShippingTree.tsx";
|
import ShippingTree from "./components/ShippingTree.tsx";
|
||||||
import { Button, Group, rem, ScrollArea, Stack } from "@mantine/core";
|
import { Group, ScrollArea, Stack } from "@mantine/core";
|
||||||
import useShipping from "./hooks/useShipping.tsx";
|
import useShipping from "./hooks/useShipping.tsx";
|
||||||
import useShippingQrs from "./hooks/useShippingQrs.tsx";
|
import useShippingQrs from "./hooks/useShippingQrs.tsx";
|
||||||
import { IconPrinter, IconQrcode } from "@tabler/icons-react";
|
import { IconPrinter, IconQrcode } from "@tabler/icons-react";
|
||||||
import { ReactNode } from "react";
|
import InlineButton from "../../../../components/InlineButton/InlineButton.tsx";
|
||||||
|
|
||||||
|
|
||||||
const ShippingTab = () => {
|
const ShippingTab = () => {
|
||||||
@@ -18,28 +18,27 @@ const ShippingTab = () => {
|
|||||||
onGetBoxesPdfClick,
|
onGetBoxesPdfClick,
|
||||||
} = useShippingQrs();
|
} = useShippingQrs();
|
||||||
|
|
||||||
const getButton = (label: string, func: () => void, icon?: ReactNode) => {
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
variant={"default"}
|
|
||||||
onClick={func}
|
|
||||||
>
|
|
||||||
<Group gap={rem(10)}>
|
|
||||||
{icon}
|
|
||||||
{label}
|
|
||||||
</Group>
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack h={"94vh"}>
|
<Stack h={"94vh"}>
|
||||||
<Group>
|
<Group>
|
||||||
{getButton("Добавить паллет", onCreatePalletClick)}
|
<InlineButton onClick={() => onCreatePalletClick()}>
|
||||||
{getButton("Добавить короб", onCreateBoxInDealClick)}
|
Добавить паллет
|
||||||
{getButton("Сделка", onGetDealQrPdfClick, <IconQrcode />)}
|
</InlineButton>
|
||||||
{getButton("Паллеты", onGetPalletsPdfClick, <IconPrinter />)}
|
<InlineButton onClick={() => onCreateBoxInDealClick()}>
|
||||||
{getButton("Короба", onGetBoxesPdfClick, <IconPrinter />)}
|
Добавить короб
|
||||||
|
</InlineButton>
|
||||||
|
<InlineButton onClick={() => onGetDealQrPdfClick()}>
|
||||||
|
<IconQrcode />
|
||||||
|
Сделка
|
||||||
|
</InlineButton>
|
||||||
|
<InlineButton onClick={() => onGetPalletsPdfClick()}>
|
||||||
|
<IconPrinter />
|
||||||
|
Паллеты
|
||||||
|
</InlineButton>
|
||||||
|
<InlineButton onClick={() => onGetBoxesPdfClick()}>
|
||||||
|
<IconPrinter />
|
||||||
|
Короба
|
||||||
|
</InlineButton>
|
||||||
</Group>
|
</Group>
|
||||||
<ScrollArea>
|
<ScrollArea>
|
||||||
<ShippingTree />
|
<ShippingTree />
|
||||||
|
|||||||
@@ -9,4 +9,8 @@
|
|||||||
|
|
||||||
.container {
|
.container {
|
||||||
padding: rem(20);
|
padding: rem(20);
|
||||||
|
|
||||||
|
@media only screen and (max-width: 768px) {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,22 +12,26 @@ export type Props = {
|
|||||||
const PageWrapper: FC<Props> = ({ children }) => {
|
const PageWrapper: FC<Props> = ({ children }) => {
|
||||||
const authState = useSelector((state: RootState) => state.auth);
|
const authState = useSelector((state: RootState) => state.auth);
|
||||||
const uiState = useSelector((state: RootState) => state.ui);
|
const uiState = useSelector((state: RootState) => state.ui);
|
||||||
|
const navbarNeeded = authState.isAuthorized && !authState.isGuest;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppShell
|
<AppShell
|
||||||
layout={"alt"}
|
layout={"alt"}
|
||||||
withBorder={false}
|
withBorder={false}
|
||||||
navbar={
|
navbar={
|
||||||
authState.isAuthorized && !authState.isGuest
|
navbarNeeded
|
||||||
? {
|
? {
|
||||||
width: "130px",
|
width: "130px",
|
||||||
breakpoint: "sm",
|
breakpoint: "sm",
|
||||||
collapsed: { desktop: uiState.hideNavbar },
|
collapsed: { desktop: uiState.hideNavbar, mobile: true },
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
}>
|
}>
|
||||||
|
<AppShell.Header>
|
||||||
|
|
||||||
|
</AppShell.Header>
|
||||||
<AppShell.Navbar>
|
<AppShell.Navbar>
|
||||||
{authState.isAuthorized && !authState.isGuest && (
|
{navbarNeeded && (
|
||||||
<Flex
|
<Flex
|
||||||
className={styles["main-container"]}
|
className={styles["main-container"]}
|
||||||
h={"100%"}
|
h={"100%"}
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { Accordion, ActionIcon, Center } from "@mantine/core";
|
||||||
|
import { IconBox, IconPlus, IconTrash } from "@tabler/icons-react";
|
||||||
|
import { ReceiptBox, ReceiptPallet } from "../types/types.tsx";
|
||||||
|
import { useReceiptContext } from "../contexts/ReceiptContext.tsx";
|
||||||
|
import ReceiptProducts from "./ReceiptProducts.tsx";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
pallet?: ReceiptPallet;
|
||||||
|
palletIdx?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AccordionBoxes = ({ pallet, palletIdx }: Props) => {
|
||||||
|
const { boxes, boxesHandlers, onObjectEditClick, palletsHandlers, setBoxData } = useReceiptContext();
|
||||||
|
|
||||||
|
const deleteBox = (boxId: number) => {
|
||||||
|
if (palletIdx && pallet) {
|
||||||
|
palletsHandlers.setItemProp(palletIdx, "boxes", pallet.boxes.filter(box => box.id !== boxId));
|
||||||
|
} else {
|
||||||
|
boxesHandlers.filter(item => item.id !== boxId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const boxActions = (box: ReceiptBox) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ActionIcon
|
||||||
|
variant={"default"}
|
||||||
|
onClick={() => onObjectEditClick(box, true, pallet)}
|
||||||
|
mr={"sm"}
|
||||||
|
>
|
||||||
|
<IconPlus />
|
||||||
|
</ActionIcon>
|
||||||
|
<ActionIcon
|
||||||
|
variant={"default"}
|
||||||
|
onClick={() => deleteBox(box.id)}
|
||||||
|
mr={"sm"}
|
||||||
|
>
|
||||||
|
<IconTrash />
|
||||||
|
</ActionIcon>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const boxesData = pallet ? pallet.boxes : boxes;
|
||||||
|
|
||||||
|
if (boxesData.length === 0) return;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Accordion multiple={true} bd={"solid 1px gray"}>
|
||||||
|
{boxesData.map(box => (
|
||||||
|
<Accordion.Item key={box.id} value={box.id.toString()}>
|
||||||
|
<Center>
|
||||||
|
<Accordion.Control icon={<IconBox />}>
|
||||||
|
Короб {box.id}
|
||||||
|
</Accordion.Control>
|
||||||
|
{boxActions(box)}
|
||||||
|
</Center>
|
||||||
|
<Accordion.Panel>
|
||||||
|
<ReceiptProducts
|
||||||
|
products={box.residualProducts}
|
||||||
|
object={box}
|
||||||
|
setObjectData={(box: ReceiptBox) => setBoxData(box, pallet)}
|
||||||
|
/>
|
||||||
|
</Accordion.Panel>
|
||||||
|
</Accordion.Item>
|
||||||
|
))}
|
||||||
|
</Accordion>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AccordionBoxes;
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
import { Accordion, ActionIcon, Box, Button, Center, Flex, Group } from "@mantine/core";
|
||||||
|
import { IconPlus, IconSpace, IconTrash } from "@tabler/icons-react";
|
||||||
|
import { ReceiptBox, ReceiptPallet } from "../types/types.tsx";
|
||||||
|
import { useReceiptContext } from "../contexts/ReceiptContext.tsx";
|
||||||
|
import AccordionBoxes from "./AccordionBoxes.tsx";
|
||||||
|
import ReceiptProducts from "./ReceiptProducts.tsx";
|
||||||
|
|
||||||
|
const AccordionPallets = () => {
|
||||||
|
const { pallets, palletsHandlers, nextId, onObjectEditClick, setPalletData } = useReceiptContext();
|
||||||
|
|
||||||
|
const deletePallet = (palletId: number) => {
|
||||||
|
palletsHandlers.filter(item => item.id !== palletId);
|
||||||
|
};
|
||||||
|
|
||||||
|
const createBox = (pallet: ReceiptPallet) => {
|
||||||
|
const box = {
|
||||||
|
id: nextId(),
|
||||||
|
residualProducts: [],
|
||||||
|
} as ReceiptBox;
|
||||||
|
|
||||||
|
const palletIdx = pallets.findIndex(p => p.id === pallet.id);
|
||||||
|
palletsHandlers.setItemProp(palletIdx, "boxes", [box, ...pallets[palletIdx].boxes]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const palletActions = (pallet: ReceiptPallet) => {
|
||||||
|
const isCreateButtonVisible = pallet.boxes.length > 0 || pallet.residualProducts.length > 0;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{isCreateButtonVisible && <ActionIcon
|
||||||
|
variant={"default"}
|
||||||
|
onClick={() => {
|
||||||
|
if (pallet.residualProducts.length > 0) {
|
||||||
|
onObjectEditClick(pallet, false);
|
||||||
|
} else {
|
||||||
|
createBox(pallet);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
mr={"sm"}
|
||||||
|
>
|
||||||
|
<IconPlus />
|
||||||
|
</ActionIcon>}
|
||||||
|
<ActionIcon
|
||||||
|
variant={"default"}
|
||||||
|
onClick={() => deletePallet(pallet.id)}
|
||||||
|
mr={"md"}
|
||||||
|
>
|
||||||
|
<IconTrash />
|
||||||
|
</ActionIcon>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const palletButton = (label: string, onClick: () => void) => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant={"default"}
|
||||||
|
onClick={onClick}
|
||||||
|
flex={1}
|
||||||
|
>
|
||||||
|
<Group gap={"md"}>
|
||||||
|
<IconPlus />
|
||||||
|
{label}
|
||||||
|
</Group>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const palletButtons = (pallet: ReceiptPallet) => {
|
||||||
|
if (pallet.boxes.length > 0 || pallet.residualProducts.length > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Flex gap={"md"}>
|
||||||
|
{palletButton("Короб", () => createBox(pallet))}
|
||||||
|
{palletButton("Товар", () => onObjectEditClick(pallet, false))}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const palletItem = (pallet: ReceiptPallet, palletIdx: number) => {
|
||||||
|
return (
|
||||||
|
<Accordion.Item key={pallet.id} value={pallet.id.toString()}>
|
||||||
|
<Center>
|
||||||
|
<Accordion.Control icon={<IconSpace />}>
|
||||||
|
Паллет {pallet.id}
|
||||||
|
</Accordion.Control>
|
||||||
|
{palletActions(pallet)}
|
||||||
|
</Center>
|
||||||
|
<Accordion.Panel>
|
||||||
|
{palletButtons(pallet)}
|
||||||
|
{pallet.boxes &&
|
||||||
|
<Box pt={"md"}>
|
||||||
|
<AccordionBoxes pallet={pallet} palletIdx={palletIdx} />
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
{pallet.residualProducts && (
|
||||||
|
<ReceiptProducts
|
||||||
|
products={pallet.residualProducts}
|
||||||
|
object={pallet}
|
||||||
|
setObjectData={object => setPalletData(object as ReceiptPallet)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Accordion.Panel>
|
||||||
|
</Accordion.Item>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (pallets.length === 0) return;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Accordion multiple={true} bd={"solid 1px gray"}>
|
||||||
|
{pallets.map(((pallet, idx) => palletItem(pallet, idx)))}
|
||||||
|
</Accordion>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AccordionPallets;
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { Button, Group } from "@mantine/core";
|
||||||
|
import { IconCheck } from "@tabler/icons-react";
|
||||||
|
import { useReceiptContext } from "../contexts/ReceiptContext.tsx";
|
||||||
|
import { type ProductAndQuantitySchema, ReceiptBoxSchema, ReceiptPalletSchema, ResiduesService } from "../../../../../client";
|
||||||
|
import { ReceiptBox, ReceiptPallet, ReceiptProduct } from "../types/types.tsx";
|
||||||
|
import { notifications } from "../../../../../shared/lib/notifications.ts";
|
||||||
|
|
||||||
|
const FinishReceiptButton = () => {
|
||||||
|
const { pallets, boxes, client, reset } = useReceiptContext();
|
||||||
|
const finishReceiptDisabled = pallets.length === 0 && boxes.length === 0;
|
||||||
|
|
||||||
|
const processProducts = (products: ReceiptProduct[]): ProductAndQuantitySchema[] => {
|
||||||
|
return products.map(p => ({
|
||||||
|
productId: p.product.id,
|
||||||
|
quantity: p.quantity,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const processBoxes = (boxes: ReceiptBox[]): ReceiptBoxSchema[] => {
|
||||||
|
return boxes.map(box => ({
|
||||||
|
products: processProducts(box.residualProducts),
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const processPallets = (pallets: ReceiptPallet[]): ReceiptPalletSchema[] => {
|
||||||
|
return pallets.map(pallet => ({
|
||||||
|
products: processProducts(pallet.residualProducts),
|
||||||
|
boxes: processBoxes(pallet.boxes),
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const finishReceipt = () => {
|
||||||
|
if (!client) return;
|
||||||
|
|
||||||
|
ResiduesService.receipt({
|
||||||
|
requestBody: {
|
||||||
|
clientId: client.id,
|
||||||
|
pallets: processPallets(pallets),
|
||||||
|
boxes: processBoxes(boxes),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(({ ok, message }) => {
|
||||||
|
notifications.guess(ok, { message });
|
||||||
|
if (!ok) return;
|
||||||
|
reset();
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="default"
|
||||||
|
onClick={finishReceipt}
|
||||||
|
disabled={finishReceiptDisabled}
|
||||||
|
>
|
||||||
|
<Group gap={"sm"}>
|
||||||
|
<IconCheck />
|
||||||
|
Завершить приемку
|
||||||
|
</Group>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FinishReceiptButton;
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import { Flex, Stack } from "@mantine/core";
|
||||||
|
import { useReceiptContext } from "../contexts/ReceiptContext.tsx";
|
||||||
|
import { ReceiptBox } from "../types/types.tsx";
|
||||||
|
import AccordionBoxes from "./AccordionBoxes.tsx";
|
||||||
|
import AccordionPallets from "./AccordionPallets.tsx";
|
||||||
|
import InlineButton from "../../../../../components/InlineButton/InlineButton.tsx";
|
||||||
|
import { IconPlus } from "@tabler/icons-react";
|
||||||
|
|
||||||
|
const ReceiptEditor = () => {
|
||||||
|
const { fixed, palletsHandlers, boxesHandlers, nextId } = useReceiptContext();
|
||||||
|
|
||||||
|
if (!fixed) return;
|
||||||
|
|
||||||
|
const createPallet = () => {
|
||||||
|
const pallet = {
|
||||||
|
id: nextId(),
|
||||||
|
boxes: [],
|
||||||
|
residualProducts: [],
|
||||||
|
};
|
||||||
|
palletsHandlers.prepend(pallet);
|
||||||
|
};
|
||||||
|
|
||||||
|
const createBox = () => {
|
||||||
|
const box = {
|
||||||
|
id: nextId(),
|
||||||
|
residualProducts: [],
|
||||||
|
} as ReceiptBox;
|
||||||
|
boxesHandlers.prepend(box);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<Flex gap="md">
|
||||||
|
<InlineButton onClick={() => createPallet()} flex={1}>
|
||||||
|
<IconPlus />
|
||||||
|
Паллет
|
||||||
|
</InlineButton>
|
||||||
|
<InlineButton onClick={() => createBox()} flex={1}>
|
||||||
|
<IconPlus />
|
||||||
|
Короб
|
||||||
|
</InlineButton>
|
||||||
|
</Flex>
|
||||||
|
<AccordionBoxes />
|
||||||
|
<AccordionPallets />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReceiptEditor;
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import { ReceiptBox, ReceiptPallet, ReceiptProduct } from "../types/types.tsx";
|
||||||
|
import { ActionIcon, Divider, Group, NumberInput, rem, Stack, Text } from "@mantine/core";
|
||||||
|
import { IconTrash } from "@tabler/icons-react";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
products: ReceiptProduct[];
|
||||||
|
object: ReceiptBox | ReceiptPallet;
|
||||||
|
setObjectData: (object: ReceiptBox | ReceiptPallet) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ReceiptProducts = ({ products, object, setObjectData }: Props) => {
|
||||||
|
if (products.length === 0) return;
|
||||||
|
|
||||||
|
const deleteProduct = (productId: number) => {
|
||||||
|
object.residualProducts = object.residualProducts.filter(product => product.id !== productId);
|
||||||
|
setObjectData({ ...object });
|
||||||
|
};
|
||||||
|
|
||||||
|
const productQuantityInput = (receiptProduct: ReceiptProduct) => {
|
||||||
|
return (
|
||||||
|
<Group>
|
||||||
|
<Text>Количество:</Text>
|
||||||
|
<NumberInput
|
||||||
|
value={receiptProduct.quantity}
|
||||||
|
onChange={value => {
|
||||||
|
const idx = object.residualProducts.findIndex(p => p.id === receiptProduct.id);
|
||||||
|
object.residualProducts[idx].quantity = Number(value);
|
||||||
|
setObjectData({ ...object });
|
||||||
|
}}
|
||||||
|
min={1}
|
||||||
|
allowDecimal={false}
|
||||||
|
allowNegative={false}
|
||||||
|
w={rem(100)}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
{products.map((receiptProduct: ReceiptProduct) => (
|
||||||
|
<Stack key={receiptProduct.id}>
|
||||||
|
<Divider />
|
||||||
|
<Group wrap={"nowrap"} justify={"space-between"}>
|
||||||
|
<Stack gap={"sm"} key={`Product-${receiptProduct.id}`}>
|
||||||
|
<Text>Товар: {receiptProduct.product.name}</Text>
|
||||||
|
{productQuantityInput(receiptProduct)}
|
||||||
|
{receiptProduct.product.size && <Text>Размер: {receiptProduct.product.size}</Text>}
|
||||||
|
</Stack>
|
||||||
|
<ActionIcon
|
||||||
|
variant={"default"}
|
||||||
|
onClick={() => deleteProduct(receiptProduct.id)}
|
||||||
|
>
|
||||||
|
<IconTrash />
|
||||||
|
</ActionIcon>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReceiptProducts;
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import { useReceiptContext } from "../contexts/ReceiptContext.tsx";
|
||||||
|
import ClientSelectNew from "../../../../../components/Selects/ClientSelectNew/ClientSelectNew.tsx";
|
||||||
|
import { Button, Flex, Group, Stack, Text } from "@mantine/core";
|
||||||
|
import { modals } from "@mantine/modals";
|
||||||
|
import { IconArrowLeft, IconX } from "@tabler/icons-react";
|
||||||
|
import FinishReceiptButton from "./FinishReceiptButton.tsx";
|
||||||
|
|
||||||
|
|
||||||
|
const ReceiptUserSelect = () => {
|
||||||
|
const {
|
||||||
|
client,
|
||||||
|
setClient,
|
||||||
|
fixed,
|
||||||
|
setFixed,
|
||||||
|
palletsHandlers,
|
||||||
|
boxesHandlers,
|
||||||
|
} = useReceiptContext();
|
||||||
|
|
||||||
|
const confirmModalText = (
|
||||||
|
<Stack>
|
||||||
|
<Text size="sm">Вы уверены, что хотите отменить приемку?</Text>
|
||||||
|
<Text size="sm">Все загруженные паллеты, короба и товары удалятся.</Text>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
|
||||||
|
const unfix = () => {
|
||||||
|
modals.openConfirmModal({
|
||||||
|
title: "Отмена приемки",
|
||||||
|
children: confirmModalText,
|
||||||
|
labels: { confirm: "Да", cancel: "Нет" },
|
||||||
|
confirmProps: { color: "red" },
|
||||||
|
onConfirm: () => {
|
||||||
|
palletsHandlers.setState([]);
|
||||||
|
boxesHandlers.setState([]);
|
||||||
|
setFixed(false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<Flex gap={"md"} flex={8}>
|
||||||
|
<Button
|
||||||
|
variant={"default"}
|
||||||
|
onClick={() => window.location.reload()}
|
||||||
|
flex={1}
|
||||||
|
disabled={fixed}
|
||||||
|
>
|
||||||
|
<IconArrowLeft />
|
||||||
|
</Button>
|
||||||
|
<ClientSelectNew
|
||||||
|
placeholder={"Выберите клиента"}
|
||||||
|
value={client}
|
||||||
|
onChange={setClient}
|
||||||
|
disabled={fixed}
|
||||||
|
flex={7}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
{
|
||||||
|
fixed ? (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
variant={"default"}
|
||||||
|
onClick={unfix}
|
||||||
|
>
|
||||||
|
<Group gap={"sm"}>
|
||||||
|
<IconX />
|
||||||
|
Отменить приемку
|
||||||
|
</Group>
|
||||||
|
</Button>
|
||||||
|
<FinishReceiptButton />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
variant={"default"}
|
||||||
|
onClick={() => setFixed(true)}
|
||||||
|
disabled={!client}
|
||||||
|
>
|
||||||
|
Выбрать
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReceiptUserSelect;
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
import { createContext, Dispatch, FC, SetStateAction, useContext, useState } from "react";
|
||||||
|
import { ClientSchema } from "../../../../../client";
|
||||||
|
import { ReceiptBox, ReceiptPallet } from "../types/types.tsx";
|
||||||
|
import { modals } from "@mantine/modals";
|
||||||
|
import nextId from "../utils/nextId.ts";
|
||||||
|
import { useListState, UseListStateHandlers } from "@mantine/hooks";
|
||||||
|
|
||||||
|
type ReceiptContextState = {
|
||||||
|
client?: ClientSchema;
|
||||||
|
setClient: Dispatch<SetStateAction<ClientSchema | undefined>>;
|
||||||
|
fixed: boolean;
|
||||||
|
setFixed: Dispatch<SetStateAction<boolean>>;
|
||||||
|
pallets: ReceiptPallet[];
|
||||||
|
palletsHandlers: UseListStateHandlers<ReceiptPallet>;
|
||||||
|
boxes: ReceiptBox[];
|
||||||
|
boxesHandlers: UseListStateHandlers<ReceiptBox>;
|
||||||
|
nextId: () => number;
|
||||||
|
onObjectEditClick: (object: ReceiptBox | ReceiptPallet, isBox: boolean, parentPallet?: ReceiptPallet) => void;
|
||||||
|
reset: () => void;
|
||||||
|
setBoxData: (box: ReceiptBox, pallet?: ReceiptPallet) => void;
|
||||||
|
setPalletData: (pallet: ReceiptPallet) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ReceiptContext = createContext<ReceiptContextState | undefined>(
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
const useReceiptContextState = () => {
|
||||||
|
const [client, setClient] = useState<ClientSchema>();
|
||||||
|
const [fixed, setFixed] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const [pallets, palletsHandlers] = useListState<ReceiptPallet>([]);
|
||||||
|
const [boxes, boxesHandlers] = useListState<ReceiptBox>([]);
|
||||||
|
|
||||||
|
const setBoxData = (box: ReceiptBox, pallet?: ReceiptPallet) => {
|
||||||
|
if (pallet) {
|
||||||
|
setBoxOnPalletData(box, pallet);
|
||||||
|
} else {
|
||||||
|
setBoxWithoutPalletData(box);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const setBoxWithoutPalletData = (box: ReceiptBox) => {
|
||||||
|
const boxIdx = boxes.findIndex(item => item.id === box.id);
|
||||||
|
if (boxIdx === -1) {
|
||||||
|
boxesHandlers.append(box);
|
||||||
|
} else {
|
||||||
|
boxesHandlers.setItem(boxIdx, box);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const setBoxOnPalletData = (box: ReceiptBox, pallet: ReceiptPallet) => {
|
||||||
|
const palletIdx = pallets.findIndex(p => p.id === pallet.id);
|
||||||
|
const boxIdx = pallets[palletIdx].boxes.findIndex(b => b.id === box.id);
|
||||||
|
if (boxIdx === -1) {
|
||||||
|
palletsHandlers.setItemProp(palletIdx, "boxes", [box, ...boxes]);
|
||||||
|
} else {
|
||||||
|
pallets[palletIdx].boxes[boxIdx] = box;
|
||||||
|
palletsHandlers.setState(pallets);
|
||||||
|
}
|
||||||
|
palletsHandlers.setState([...pallets]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setPalletData = (pallet: ReceiptPallet) => {
|
||||||
|
const palletIdx = pallets.findIndex(p => p.id === pallet.id);
|
||||||
|
if (palletIdx !== -1) {
|
||||||
|
palletsHandlers.setItem(palletIdx, pallet);
|
||||||
|
palletsHandlers.setState([...pallets]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onObjectEditClick = (
|
||||||
|
object: ReceiptBox | ReceiptPallet,
|
||||||
|
isBox: boolean,
|
||||||
|
parentPallet?: ReceiptPallet,
|
||||||
|
) => {
|
||||||
|
if (!client) return;
|
||||||
|
modals.openContextModal({
|
||||||
|
modal: "newReceiptModal",
|
||||||
|
title: "Добавление товара",
|
||||||
|
withCloseButton: false,
|
||||||
|
innerProps: {
|
||||||
|
clientId: client.id,
|
||||||
|
object: object,
|
||||||
|
setObjectData: (object: ReceiptBox | ReceiptPallet) => {
|
||||||
|
if (isBox) setBoxData(object, parentPallet);
|
||||||
|
setPalletData(object as ReceiptPallet);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const reset = () => {
|
||||||
|
palletsHandlers.setState([]);
|
||||||
|
boxesHandlers.setState([]);
|
||||||
|
setFixed(false);
|
||||||
|
setClient(undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
client,
|
||||||
|
setClient,
|
||||||
|
fixed,
|
||||||
|
setFixed,
|
||||||
|
pallets,
|
||||||
|
palletsHandlers,
|
||||||
|
boxes,
|
||||||
|
boxesHandlers,
|
||||||
|
nextId,
|
||||||
|
onObjectEditClick,
|
||||||
|
reset,
|
||||||
|
setBoxData,
|
||||||
|
setPalletData,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type ReceiptContextProviderProps = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ReceiptContextProvider: FC<ReceiptContextProviderProps> = ({ children }) => {
|
||||||
|
const state = useReceiptContextState();
|
||||||
|
return (
|
||||||
|
<ReceiptContext.Provider value={state}>
|
||||||
|
{children}
|
||||||
|
</ReceiptContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useReceiptContext = () => {
|
||||||
|
const context = useContext(ReceiptContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error(
|
||||||
|
"useReceiptContext must be used within a ReceiptContextProvider",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
import { useForm } from "@mantine/form";
|
||||||
|
import { ContextModalProps } from "@mantine/modals";
|
||||||
|
import { Button, Divider, Flex, NumberInput, rem, Stack } from "@mantine/core";
|
||||||
|
import ProductSelect from "../../../../../components/ProductSelect/ProductSelect.tsx";
|
||||||
|
import { ReceiptBox, ReceiptPallet } from "../types/types.tsx";
|
||||||
|
import { ResidualModalForm } from "../../../../ResiduesPage/types/ResidualProductData.tsx";
|
||||||
|
import nextId from "../utils/nextId.ts";
|
||||||
|
import useScanning from "../../../hooks/useScanning.tsx";
|
||||||
|
import ScanBarcode from "../../ScanBarcode/ScanBarcode.tsx";
|
||||||
|
import { ProductSchema } from "../../../../../client";
|
||||||
|
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
clientId: number;
|
||||||
|
object: ReceiptBox | ReceiptPallet;
|
||||||
|
setObjectData: (object: ReceiptBox | ReceiptPallet) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NewReceiptModal = ({
|
||||||
|
context,
|
||||||
|
id,
|
||||||
|
innerProps,
|
||||||
|
}: ContextModalProps<Props>) => {
|
||||||
|
const {
|
||||||
|
clientId,
|
||||||
|
setObjectData,
|
||||||
|
object,
|
||||||
|
} = innerProps;
|
||||||
|
const { isScanning, setIsScanning, scannedValue, setScannedValue } = useScanning();
|
||||||
|
|
||||||
|
const initialValues = {
|
||||||
|
product: null,
|
||||||
|
quantity: 1,
|
||||||
|
};
|
||||||
|
const form = useForm<ResidualModalForm>({
|
||||||
|
initialValues,
|
||||||
|
validate: {
|
||||||
|
product: product => !product && "Необходимо выбрать товар",
|
||||||
|
quantity: quantity => (!quantity || quantity === 0) && "Слишком мало товара",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = () => {
|
||||||
|
const productIdx = object.residualProducts.findIndex(p => p.product.id === form.values.product?.id);
|
||||||
|
|
||||||
|
if (productIdx !== -1) {
|
||||||
|
object.residualProducts[productIdx].quantity += form.values.quantity;
|
||||||
|
} else {
|
||||||
|
const id = nextId();
|
||||||
|
const receiptProduct = {
|
||||||
|
id,
|
||||||
|
product: form.values.product!,
|
||||||
|
quantity: form.values.quantity,
|
||||||
|
};
|
||||||
|
object.residualProducts.unshift(receiptProduct);
|
||||||
|
}
|
||||||
|
|
||||||
|
setObjectData(object);
|
||||||
|
form.reset();
|
||||||
|
context.closeContextModal(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onProductAfterScanningSelect = (product?: ProductSchema) => {
|
||||||
|
if (!product) {
|
||||||
|
setScannedValue("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const productIdx = object.residualProducts.findIndex(p => p.product.id === product.id);
|
||||||
|
if (productIdx === -1) {
|
||||||
|
form.setFieldValue("product", product);
|
||||||
|
} else {
|
||||||
|
object.residualProducts[productIdx].quantity++;
|
||||||
|
setObjectData({ ...object });
|
||||||
|
context.closeContextModal(id);
|
||||||
|
}
|
||||||
|
setScannedValue("");
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
direction={"column"}
|
||||||
|
gap={rem(10)}
|
||||||
|
>
|
||||||
|
<form onSubmit={form.onSubmit(() => onSubmit())}>
|
||||||
|
<Stack gap={rem(10)}>
|
||||||
|
<Divider />
|
||||||
|
<ProductSelect
|
||||||
|
label={"Товар"}
|
||||||
|
placeholder={"Выберите товар"}
|
||||||
|
{...form.getInputProps("product")}
|
||||||
|
clientId={clientId}
|
||||||
|
disabled={isScanning}
|
||||||
|
/>
|
||||||
|
<NumberInput
|
||||||
|
label={"Количество"}
|
||||||
|
hideControls
|
||||||
|
{...form.getInputProps("quantity")}
|
||||||
|
min={1}
|
||||||
|
defaultValue={1}
|
||||||
|
disabled={isScanning}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant={"default"}
|
||||||
|
type={"submit"}
|
||||||
|
disabled={isScanning}
|
||||||
|
>
|
||||||
|
Добавить
|
||||||
|
</Button>
|
||||||
|
<Divider />
|
||||||
|
</Stack>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<ScanBarcode
|
||||||
|
clientId={clientId}
|
||||||
|
isScanning={isScanning}
|
||||||
|
setIsScanning={setIsScanning}
|
||||||
|
onProductSelect={onProductAfterScanningSelect}
|
||||||
|
scannedValue={scannedValue}
|
||||||
|
object={object}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NewReceiptModal;
|
||||||
18
src/pages/ReceiptPage/components/NewReceipt/types/types.tsx
Normal file
18
src/pages/ReceiptPage/components/NewReceipt/types/types.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { ProductSchema } from "../../../../../client";
|
||||||
|
|
||||||
|
export type ReceiptProduct = {
|
||||||
|
id: number;
|
||||||
|
quantity: number;
|
||||||
|
product: ProductSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ReceiptBox = {
|
||||||
|
id: number;
|
||||||
|
residualProducts: ReceiptProduct[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ReceiptPallet = {
|
||||||
|
id: number;
|
||||||
|
residualProducts: ReceiptProduct[];
|
||||||
|
boxes: ReceiptBox[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
let id = 1;
|
||||||
|
|
||||||
|
export default function nextId() {
|
||||||
|
return id++;
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import { Accordion, ActionIcon, Center } from "@mantine/core";
|
||||||
|
import { IconBox, IconPlus, IconTrash } from "@tabler/icons-react";
|
||||||
|
import ReceiptProducts from "./ReceiptProducts.tsx";
|
||||||
|
import { ResidualBoxSchema, ResidualPalletSchema, ResiduesService } from "../../../../../client";
|
||||||
|
import { modals } from "@mantine/modals";
|
||||||
|
import { notifications } from "../../../../../shared/lib/notifications.ts";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
pallet: ResidualPalletSchema;
|
||||||
|
clientId: number;
|
||||||
|
fetchPallet: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AccordionBoxesOnPallet = ({ pallet, clientId, fetchPallet }: Props) => {
|
||||||
|
const deleteBox = (boxId: number) => {
|
||||||
|
ResiduesService.deleteResidualBox({
|
||||||
|
boxId,
|
||||||
|
})
|
||||||
|
.then(({ ok, message }) => {
|
||||||
|
if (!ok) {
|
||||||
|
notifications.error({ message });
|
||||||
|
}
|
||||||
|
fetchPallet();
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err));
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCreateProductClick = (box: ResidualBoxSchema) => {
|
||||||
|
if (!(box && clientId)) return;
|
||||||
|
modals.openContextModal({
|
||||||
|
modal: "receiptModal",
|
||||||
|
title: "Добавление товара",
|
||||||
|
withCloseButton: false,
|
||||||
|
innerProps: {
|
||||||
|
clientId,
|
||||||
|
isBox: true,
|
||||||
|
object: box,
|
||||||
|
fetchObject: fetchPallet,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const boxActions = (box: ResidualBoxSchema) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ActionIcon
|
||||||
|
variant={"default"}
|
||||||
|
onClick={() => onCreateProductClick(box)}
|
||||||
|
mr={"sm"}
|
||||||
|
>
|
||||||
|
<IconPlus />
|
||||||
|
</ActionIcon>
|
||||||
|
<ActionIcon
|
||||||
|
variant={"default"}
|
||||||
|
onClick={() => deleteBox(box.id)}
|
||||||
|
mr={"sm"}
|
||||||
|
>
|
||||||
|
<IconTrash />
|
||||||
|
</ActionIcon>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (pallet.boxes.length === 0) return;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Accordion multiple={true} bd={"solid 1px gray"}>
|
||||||
|
{pallet.boxes.map(box => (
|
||||||
|
<Accordion.Item key={box.id} value={box.id.toString()}>
|
||||||
|
<Center>
|
||||||
|
<Accordion.Control icon={<IconBox />}>
|
||||||
|
Короб {box.id}
|
||||||
|
</Accordion.Control>
|
||||||
|
{boxActions(box)}
|
||||||
|
</Center>
|
||||||
|
<Accordion.Panel>
|
||||||
|
<ReceiptProducts
|
||||||
|
clientId={clientId}
|
||||||
|
object={box}
|
||||||
|
updateObject={fetchPallet}
|
||||||
|
/>
|
||||||
|
</Accordion.Panel>
|
||||||
|
</Accordion.Item>
|
||||||
|
))}
|
||||||
|
</Accordion>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AccordionBoxesOnPallet;
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { Group, NumberInput, rem, Text } from "@mantine/core";
|
||||||
|
import { ResidualProductSchema, ResiduesService } from "../../../../../client";
|
||||||
|
import { notifications } from "../../../../../shared/lib/notifications.ts";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
residualProduct: ResidualProductSchema;
|
||||||
|
updateObject: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProductQuantityField = ({ residualProduct, updateObject }: Props) => {
|
||||||
|
const updateProduct = (quantity: number) => {
|
||||||
|
if (residualProduct.quantity === quantity) return;
|
||||||
|
ResiduesService.updateResidualProduct({
|
||||||
|
requestBody: {
|
||||||
|
data: {
|
||||||
|
quantity,
|
||||||
|
productId: residualProduct.product.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
residualProductId: residualProduct.id,
|
||||||
|
})
|
||||||
|
.then(({ ok, message }) => {
|
||||||
|
if (!ok) {
|
||||||
|
notifications.error({ message });
|
||||||
|
}
|
||||||
|
updateObject();
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group>
|
||||||
|
<Text>Количество: </Text>
|
||||||
|
<NumberInput
|
||||||
|
value={residualProduct.quantity}
|
||||||
|
onChange={(value) => updateProduct(Number(value))}
|
||||||
|
min={1}
|
||||||
|
allowDecimal={false}
|
||||||
|
allowNegative={false}
|
||||||
|
w={rem(100)}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProductQuantityField;
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { Button, Flex, Stack, Title } from "@mantine/core";
|
||||||
|
import useReceiptBox from "../hooks/useReceiptBox.tsx";
|
||||||
|
import ReceiptProducts from "./ReceiptProducts.tsx";
|
||||||
|
import InlineButton from "../../../../../components/InlineButton/InlineButton.tsx";
|
||||||
|
import { IconArrowLeft, IconPlus } from "@tabler/icons-react";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
boxId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ReceiptBoxEditor = ({ boxId }: Props) => {
|
||||||
|
const {
|
||||||
|
box,
|
||||||
|
fetchBox,
|
||||||
|
clientId,
|
||||||
|
onCreateProductClick,
|
||||||
|
} = useReceiptBox({ boxId });
|
||||||
|
|
||||||
|
const backButton = (
|
||||||
|
<Button
|
||||||
|
variant={"default"}
|
||||||
|
onClick={() => window.location.reload()}
|
||||||
|
flex={1}
|
||||||
|
>
|
||||||
|
<IconArrowLeft />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<Flex align={"center"} flex={9} gap={"md"}>
|
||||||
|
{backButton}
|
||||||
|
<Title flex={8} order={3}>
|
||||||
|
Короб ID: К{box?.id}
|
||||||
|
</Title>
|
||||||
|
</Flex>
|
||||||
|
<InlineButton onClick={onCreateProductClick}>
|
||||||
|
<IconPlus />
|
||||||
|
Товар
|
||||||
|
</InlineButton>
|
||||||
|
<ReceiptProducts
|
||||||
|
object={box}
|
||||||
|
clientId={clientId}
|
||||||
|
updateObject={fetchBox}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReceiptBoxEditor;
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
import useReceiptPallet from "../hooks/useReceiptPallet.tsx";
|
||||||
|
import { Button, Flex, Group, Stack, Text, Title } from "@mantine/core";
|
||||||
|
import { IconArrowLeft, IconPlus } from "@tabler/icons-react";
|
||||||
|
import InlineButton from "../../../../../components/InlineButton/InlineButton.tsx";
|
||||||
|
import ReceiptProducts from "./ReceiptProducts.tsx";
|
||||||
|
import AccordionBoxesOnPallet from "./AccordionBoxes.tsx";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
palletId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ReceiptPalletEditor = ({ palletId }: Props) => {
|
||||||
|
const {
|
||||||
|
pallet,
|
||||||
|
fetchPallet,
|
||||||
|
clientId,
|
||||||
|
onCreateProductClick,
|
||||||
|
onCreateBoxClick,
|
||||||
|
} = useReceiptPallet({ palletId });
|
||||||
|
|
||||||
|
if (!pallet) return <Text>Паллет c ID П{palletId} не найден</Text>;
|
||||||
|
|
||||||
|
const createButtons = () => {
|
||||||
|
const isBoxes = pallet.boxes.length > 0;
|
||||||
|
const isProducts = pallet.residualProducts.length > 0;
|
||||||
|
const isBoth = !(isBoxes || isProducts);
|
||||||
|
return (
|
||||||
|
<Group>
|
||||||
|
{(isBoxes || isBoth) && (
|
||||||
|
<InlineButton onClick={onCreateBoxClick} flex={1}>
|
||||||
|
<IconPlus />
|
||||||
|
Короб
|
||||||
|
</InlineButton>
|
||||||
|
)}
|
||||||
|
{(isProducts || isBoth) && (
|
||||||
|
<InlineButton onClick={onCreateProductClick} flex={1}>
|
||||||
|
<IconPlus />
|
||||||
|
Товар
|
||||||
|
</InlineButton>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderPalletData = () => {
|
||||||
|
if (!clientId) return;
|
||||||
|
if (pallet?.boxes.length !== 0) {
|
||||||
|
return (
|
||||||
|
<AccordionBoxesOnPallet
|
||||||
|
pallet={pallet}
|
||||||
|
clientId={clientId}
|
||||||
|
fetchPallet={fetchPallet}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (pallet?.residualProducts.length !== 0) {
|
||||||
|
return (
|
||||||
|
<ReceiptProducts
|
||||||
|
clientId={clientId}
|
||||||
|
object={pallet}
|
||||||
|
updateObject={fetchPallet}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const backButton = (
|
||||||
|
<Button
|
||||||
|
variant={"default"}
|
||||||
|
onClick={() => window.location.reload()}
|
||||||
|
flex={1}
|
||||||
|
>
|
||||||
|
<IconArrowLeft />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<Flex align={"center"} flex={9} gap={"md"}>
|
||||||
|
{backButton}
|
||||||
|
<Title flex={8} order={3}>
|
||||||
|
Паллет ID: П{pallet?.id}
|
||||||
|
</Title>
|
||||||
|
</Flex>
|
||||||
|
{createButtons()}
|
||||||
|
{renderPalletData()}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReceiptPalletEditor;
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { ActionIcon, Divider, Flex, Group, rem, Stack, Text } from "@mantine/core";
|
||||||
|
import { ResidualBoxSchema, ResidualPalletSchema, ResidualProductSchema, ResiduesService } from "../../../../../client";
|
||||||
|
import { IconTrash } from "@tabler/icons-react";
|
||||||
|
import { notifications } from "../../../../../shared/lib/notifications.ts";
|
||||||
|
import ProductQuantityField from "./ProductQuantityField.tsx";
|
||||||
|
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
clientId: number | null;
|
||||||
|
object: ResidualBoxSchema | ResidualPalletSchema | null;
|
||||||
|
updateObject: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ReceiptProducts = ({ clientId, object, updateObject }: Props) => {
|
||||||
|
if (!object || !clientId) return;
|
||||||
|
|
||||||
|
const deleteProduct = (residualProductId: number) => {
|
||||||
|
ResiduesService.deleteResidualProduct({
|
||||||
|
residualProductId,
|
||||||
|
})
|
||||||
|
.then(({ ok, message }) => {
|
||||||
|
notifications.guess(ok, { message });
|
||||||
|
updateObject();
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err));
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderProducts = () => {
|
||||||
|
return object.residualProducts
|
||||||
|
.sort((a, b) => a.id - b.id)
|
||||||
|
.map((residualProduct: ResidualProductSchema) => (
|
||||||
|
<Stack key={residualProduct.id} gap={"sm"}>
|
||||||
|
<Divider />
|
||||||
|
<Group wrap={"nowrap"} justify={"space-between"}>
|
||||||
|
<Stack>
|
||||||
|
<Text>Товар: {residualProduct.product.name}</Text>
|
||||||
|
<ProductQuantityField
|
||||||
|
residualProduct={residualProduct}
|
||||||
|
updateObject={updateObject}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<ActionIcon
|
||||||
|
variant={"default"}
|
||||||
|
onClick={() => deleteProduct(residualProduct.id)}
|
||||||
|
>
|
||||||
|
<IconTrash />
|
||||||
|
</ActionIcon>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
direction={"column"}
|
||||||
|
gap={rem(10)}
|
||||||
|
>
|
||||||
|
{renderProducts()}
|
||||||
|
<Divider />
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReceiptProducts;
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { Button, Center, Stack, Text } from "@mantine/core";
|
||||||
|
import useScanning from "../../../hooks/useScanning.tsx";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { notifications } from "../../../../../shared/lib/notifications.ts";
|
||||||
|
import ReceiptPalletEditor from "./ReceiptPalletEditor.tsx";
|
||||||
|
import ReceiptBoxEditor from "./ReceiptBoxEditor.tsx";
|
||||||
|
import { IconArrowLeft } from "@tabler/icons-react";
|
||||||
|
|
||||||
|
const ReceiptQrCodeScan = () => {
|
||||||
|
const {
|
||||||
|
setIsScanning,
|
||||||
|
scannedValue,
|
||||||
|
setScannedValue,
|
||||||
|
} = useScanning();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsScanning(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const getId = () => Number.parseInt(scannedValue.substring(1));
|
||||||
|
|
||||||
|
const checkQrScannedCorrectly = () => {
|
||||||
|
if (
|
||||||
|
scannedValue.length < 2
|
||||||
|
|| (scannedValue[0] !== "П" && scannedValue[0] !== "К")
|
||||||
|
|| Number.isNaN(getId())
|
||||||
|
) {
|
||||||
|
notifications.error({ message: `Считанный ID ${scannedValue} некорректный` });
|
||||||
|
setScannedValue("");
|
||||||
|
setIsScanning(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (scannedValue) {
|
||||||
|
checkQrScannedCorrectly();
|
||||||
|
}
|
||||||
|
}, [scannedValue]);
|
||||||
|
|
||||||
|
if (!scannedValue) {
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<Button
|
||||||
|
variant={"default"}
|
||||||
|
onClick={() => window.location.reload()}
|
||||||
|
>
|
||||||
|
<IconArrowLeft />
|
||||||
|
</Button>
|
||||||
|
<Center>
|
||||||
|
<Text>
|
||||||
|
Отсканируйте QR код
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scannedValue.length > 0 && scannedValue[0] === "К") {
|
||||||
|
return <ReceiptBoxEditor boxId={getId()} />;
|
||||||
|
}
|
||||||
|
return <ReceiptPalletEditor palletId={getId()} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReceiptQrCodeScan;
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { ResidualBoxSchema, ResiduesService } from "../../../../../client";
|
||||||
|
import { modals } from "@mantine/modals";
|
||||||
|
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
boxId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useReceiptBox = ({ boxId }: Props) => {
|
||||||
|
const [box, setBox] = useState<ResidualBoxSchema | null>(null);
|
||||||
|
const [clientId, setClientId] = useState<number | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchBox(boxId);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const fetchBox = (boxId?: number) => {
|
||||||
|
const id = boxId ?? box?.id;
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
ResiduesService.getResidualBox({ boxId: id })
|
||||||
|
.then(res => {
|
||||||
|
setBox(res.box);
|
||||||
|
setClientId(res.clientId);
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err));
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCreateProductClick = () => {
|
||||||
|
if (!(box && clientId)) return;
|
||||||
|
modals.openContextModal({
|
||||||
|
modal: "receiptModal",
|
||||||
|
title: "Добавление товара",
|
||||||
|
withCloseButton: false,
|
||||||
|
innerProps: {
|
||||||
|
clientId,
|
||||||
|
isBox: true,
|
||||||
|
object: box,
|
||||||
|
fetchObject: fetchBox,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
box,
|
||||||
|
fetchBox,
|
||||||
|
clientId,
|
||||||
|
onCreateProductClick,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useReceiptBox;
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { ResidualPalletSchema, ResiduesService } from "../../../../../client";
|
||||||
|
import { modals } from "@mantine/modals";
|
||||||
|
import { notifications } from "../../../../../shared/lib/notifications.ts";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
palletId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useReceiptPallet = ({ palletId }: Props) => {
|
||||||
|
const [pallet, setPallet] = useState<ResidualPalletSchema | null>(null);
|
||||||
|
const [clientId, setClientId] = useState<number | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchPallet(palletId);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const fetchPallet = (palletId?: number) => {
|
||||||
|
const id = palletId ?? pallet?.id;
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
ResiduesService.getResidualPallet({ palletId: id })
|
||||||
|
.then(res => {
|
||||||
|
setPallet(res.pallet);
|
||||||
|
setClientId(res.clientId);
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err));
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCreateProductClick = () => {
|
||||||
|
if (!(pallet && clientId)) return;
|
||||||
|
modals.openContextModal({
|
||||||
|
modal: "receiptModal",
|
||||||
|
title: "Добавление товара",
|
||||||
|
withCloseButton: false,
|
||||||
|
innerProps: {
|
||||||
|
clientId,
|
||||||
|
isBox: false,
|
||||||
|
object: pallet,
|
||||||
|
fetchObject: fetchPallet,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCreateBoxClick = () => {
|
||||||
|
ResiduesService.createResidualBox({
|
||||||
|
requestBody: {
|
||||||
|
palletId,
|
||||||
|
clientId: null,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(({ ok, message }) => {
|
||||||
|
if (!ok) {
|
||||||
|
notifications.error({ message });
|
||||||
|
}
|
||||||
|
fetchPallet();
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err));
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
pallet,
|
||||||
|
fetchPallet,
|
||||||
|
clientId,
|
||||||
|
onCreateProductClick,
|
||||||
|
onCreateBoxClick,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useReceiptPallet;
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
import { useForm } from "@mantine/form";
|
||||||
|
import { ContextModalProps } from "@mantine/modals";
|
||||||
|
import { Button, Divider, Flex, NumberInput, rem, Stack } from "@mantine/core";
|
||||||
|
import ProductSelect from "../../../../../components/ProductSelect/ProductSelect.tsx";
|
||||||
|
import useScanning from "../../../hooks/useScanning.tsx";
|
||||||
|
import ScanBarcode from "../../ScanBarcode/ScanBarcode.tsx";
|
||||||
|
import { ProductSchema, ResidualBoxSchema, ResidualPalletSchema, ResiduesService } from "../../../../../client";
|
||||||
|
import { notifications } from "../../../../../shared/lib/notifications.ts";
|
||||||
|
import { ResidualModalForm } from "../../../../ResiduesPage/types/ResidualProductData.tsx";
|
||||||
|
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
clientId: number;
|
||||||
|
object: ResidualBoxSchema | ResidualPalletSchema;
|
||||||
|
fetchObject: () => void;
|
||||||
|
isBox: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ReceiptModal = ({
|
||||||
|
context,
|
||||||
|
id,
|
||||||
|
innerProps,
|
||||||
|
}: ContextModalProps<Props>) => {
|
||||||
|
const {
|
||||||
|
clientId,
|
||||||
|
object,
|
||||||
|
fetchObject,
|
||||||
|
} = innerProps;
|
||||||
|
const { isScanning, setIsScanning, scannedValue, setScannedValue } = useScanning();
|
||||||
|
const initialValues = {
|
||||||
|
product: null,
|
||||||
|
quantity: 1,
|
||||||
|
};
|
||||||
|
const form = useForm<ResidualModalForm>({
|
||||||
|
initialValues,
|
||||||
|
validate: {
|
||||||
|
product: product => !product && "Необходимо выбрать товар",
|
||||||
|
quantity: quantity => (!quantity || quantity === 0) && "Слишком мало товара",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!object || !clientId) return;
|
||||||
|
|
||||||
|
const postProduct = (product: ProductSchema, quantity: number) => {
|
||||||
|
ResiduesService.createResidualProduct({
|
||||||
|
requestBody: {
|
||||||
|
data: {
|
||||||
|
productId: product.id,
|
||||||
|
quantity,
|
||||||
|
palletId: innerProps.isBox ? null : object.id,
|
||||||
|
boxId: innerProps.isBox ? object.id : null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(({ ok, message }) => {
|
||||||
|
notifications.guess(ok, { message });
|
||||||
|
fetchObject();
|
||||||
|
if (ok) context.closeContextModal(id);
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err));
|
||||||
|
};
|
||||||
|
|
||||||
|
const onProductAfterScanningSelect = (product?: ProductSchema) => {
|
||||||
|
if (!product) {
|
||||||
|
setScannedValue("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const productIdx = object.residualProducts.findIndex(p => p.product.id === product.id);
|
||||||
|
if (productIdx === -1) {
|
||||||
|
form.setFieldValue("product", product);
|
||||||
|
} else {
|
||||||
|
postProduct(product, 1);
|
||||||
|
}
|
||||||
|
setScannedValue("");
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = () => {
|
||||||
|
postProduct(form.values.product!, form.values.quantity!);
|
||||||
|
form.reset();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
direction={"column"}
|
||||||
|
gap={rem(10)}
|
||||||
|
>
|
||||||
|
<form onSubmit={form.onSubmit(() => onSubmit())}>
|
||||||
|
<Stack gap={rem(10)}>
|
||||||
|
<Divider />
|
||||||
|
<ProductSelect
|
||||||
|
label={"Товар"}
|
||||||
|
placeholder={"Выберите товар"}
|
||||||
|
{...form.getInputProps("product")}
|
||||||
|
clientId={clientId}
|
||||||
|
disabled={isScanning}
|
||||||
|
/>
|
||||||
|
<NumberInput
|
||||||
|
label={"Количество"}
|
||||||
|
hideControls
|
||||||
|
{...form.getInputProps("quantity")}
|
||||||
|
min={1}
|
||||||
|
defaultValue={1}
|
||||||
|
disabled={isScanning}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant={"default"}
|
||||||
|
type={"submit"}
|
||||||
|
disabled={isScanning}
|
||||||
|
>
|
||||||
|
Добавить
|
||||||
|
</Button>
|
||||||
|
<Divider />
|
||||||
|
</Stack>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<ScanBarcode
|
||||||
|
clientId={clientId}
|
||||||
|
isScanning={isScanning}
|
||||||
|
setIsScanning={setIsScanning}
|
||||||
|
onProductSelect={onProductAfterScanningSelect}
|
||||||
|
scannedValue={scannedValue}
|
||||||
|
object={object}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReceiptModal;
|
||||||
135
src/pages/ReceiptPage/components/ScanBarcode/ScanBarcode.tsx
Normal file
135
src/pages/ReceiptPage/components/ScanBarcode/ScanBarcode.tsx
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import { ProductSchema, ResidualBoxSchema, ResidualPalletSchema } from "../../../../client";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import useProductsList from "../../../ProductsPage/hooks/useProductsList.tsx";
|
||||||
|
import { Button, Group, Radio, Stack, Text } from "@mantine/core";
|
||||||
|
import { notifications } from "../../../../shared/lib/notifications.ts";
|
||||||
|
import { ReceiptBox, ReceiptPallet } from "../NewReceipt/types/types.tsx";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onProductSelect: (product?: ProductSchema) => void;
|
||||||
|
clientId: number;
|
||||||
|
isScanning: boolean;
|
||||||
|
setIsScanning: (isScanning: boolean) => void;
|
||||||
|
scannedValue: string;
|
||||||
|
object: ReceiptBox | ReceiptPallet | ResidualPalletSchema | ResidualBoxSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ScanBarcode = ({
|
||||||
|
onProductSelect,
|
||||||
|
clientId,
|
||||||
|
isScanning,
|
||||||
|
setIsScanning,
|
||||||
|
scannedValue,
|
||||||
|
object,
|
||||||
|
}: Props) => {
|
||||||
|
const productsData = useProductsList({ clientId, searchInput: "" });
|
||||||
|
const [barcodesProducts, setBarcodesProducts] = useState(new Map<string, ProductSchema[]>());
|
||||||
|
let productsToSelect: ProductSchema[] = [];
|
||||||
|
const [selectedProduct, setSelectedProduct] = useState<ProductSchema>();
|
||||||
|
|
||||||
|
const toggleScanning = () => {
|
||||||
|
setIsScanning(!isScanning);
|
||||||
|
};
|
||||||
|
|
||||||
|
const productsToBarcodesProducts = () => {
|
||||||
|
const data = new Map<string, ProductSchema[]>();
|
||||||
|
productsData.products.forEach(product => {
|
||||||
|
product.barcodes.forEach(barcode => {
|
||||||
|
if (data.has(barcode)) {
|
||||||
|
data.set(barcode, [...data.get(barcode)!, product]);
|
||||||
|
} else {
|
||||||
|
data.set(barcode, [product]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
setBarcodesProducts(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (productsData.products.length !== 0) {
|
||||||
|
productsToBarcodesProducts();
|
||||||
|
}
|
||||||
|
}, [productsData.isLoading]);
|
||||||
|
|
||||||
|
const findProductInObject = (productsToSelect: ProductSchema[]): ProductSchema | undefined => {
|
||||||
|
for (let i = 0; i < productsToSelect.length; i++) {
|
||||||
|
for (let j = 0; j < object.residualProducts.length; j++) {
|
||||||
|
if (productsToSelect[i].id === object.residualProducts[j].product.id) {
|
||||||
|
return productsToSelect[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderScanningResults = () => {
|
||||||
|
productsToSelect = barcodesProducts.get(scannedValue) ?? [];
|
||||||
|
if (productsToSelect?.length === 0) {
|
||||||
|
notifications.error({ message: `Товара с штрихкодом ${scannedValue} не найдено` });
|
||||||
|
onProductSelect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (productsToSelect?.length === 1) {
|
||||||
|
onProductSelect(productsToSelect[0]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const product = findProductInObject(productsToSelect);
|
||||||
|
if (product) {
|
||||||
|
onProductSelect(product);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return renderProductSelect();
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderProductsToSelect = () => {
|
||||||
|
return productsToSelect.map(product => (
|
||||||
|
<Group key={product.id} wrap={"nowrap"}>
|
||||||
|
<Radio
|
||||||
|
checked={selectedProduct?.id === product.id}
|
||||||
|
onChange={() => setSelectedProduct(product)}
|
||||||
|
/>
|
||||||
|
<Stack>
|
||||||
|
<Text>{product.name}</Text>
|
||||||
|
{product.size && <Text>{product.size}</Text>}
|
||||||
|
</Stack>
|
||||||
|
</Group>
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderProductSelect = () => {
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<Text>Выберите товар для данного штрихкода</Text>
|
||||||
|
{renderProductsToSelect()}
|
||||||
|
<Button
|
||||||
|
variant={"default"}
|
||||||
|
onClick={() => {
|
||||||
|
if (!selectedProduct) {
|
||||||
|
notifications.error({ message: "Товар не выбран" });
|
||||||
|
} else {
|
||||||
|
onProductSelect(selectedProduct);
|
||||||
|
setSelectedProduct(undefined);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Выбрать
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (scannedValue) ? (
|
||||||
|
renderScanningResults()
|
||||||
|
) : (
|
||||||
|
<Stack gap={0}>
|
||||||
|
<Button
|
||||||
|
variant={"default"}
|
||||||
|
type={"button"}
|
||||||
|
onClick={toggleScanning}
|
||||||
|
>
|
||||||
|
{isScanning ? "Отменить сканирование" : "Отсканировать штрихкод"}
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ScanBarcode;
|
||||||
34
src/pages/ReceiptPage/hooks/useScanning.tsx
Normal file
34
src/pages/ReceiptPage/hooks/useScanning.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { useWindowEvent } from "@mantine/hooks";
|
||||||
|
|
||||||
|
const useScanning = () => {
|
||||||
|
const [scanningValue, setScanningValue] = useState<string>("");
|
||||||
|
const [scannedValue, setScannedValue] = useState<string>("");
|
||||||
|
const [isScanning, setIsScanning] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const setIsScanningValue = (isScanning: boolean) => {
|
||||||
|
if (!isScanning) {
|
||||||
|
setScanningValue("");
|
||||||
|
}
|
||||||
|
setIsScanning(isScanning);
|
||||||
|
};
|
||||||
|
|
||||||
|
useWindowEvent("keydown", (event) => {
|
||||||
|
if (!isScanning) return;
|
||||||
|
event.preventDefault();
|
||||||
|
setScanningValue(prevState => prevState + event.key);
|
||||||
|
if (["\n", "\r", "Enter"].includes(event.key)) {
|
||||||
|
setScannedValue(scanningValue);
|
||||||
|
setIsScanningValue(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
isScanning,
|
||||||
|
setIsScanning: setIsScanningValue,
|
||||||
|
scannedValue,
|
||||||
|
setScannedValue,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useScanning;
|
||||||
0
src/pages/ReceiptPage/index.tsx
Normal file
0
src/pages/ReceiptPage/index.tsx
Normal file
7
src/pages/ReceiptPage/ui/ReceiptPage.module.css
Normal file
7
src/pages/ReceiptPage/ui/ReceiptPage.module.css
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
gap: rem(10);
|
||||||
|
}
|
||||||
|
|
||||||
61
src/pages/ReceiptPage/ui/ReceiptPage.tsx
Normal file
61
src/pages/ReceiptPage/ui/ReceiptPage.tsx
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { ReceiptContextProvider } from "../components/NewReceipt/contexts/ReceiptContext.tsx";
|
||||||
|
import ReceiptUserSelect from "../components/NewReceipt/components/ReceiptUserSelect.tsx";
|
||||||
|
import ReceiptEditor from "../components/NewReceipt/components/ReceiptEditor.tsx";
|
||||||
|
import { Button, Stack } from "@mantine/core";
|
||||||
|
import { useState } from "react";
|
||||||
|
import ReceiptQrCodeScan from "../components/ReceiptEditing/components/ReceiptQrCodeScan.tsx";
|
||||||
|
import PageBlock from "../../../components/PageBlock/PageBlock.tsx";
|
||||||
|
|
||||||
|
enum ReceiptType {
|
||||||
|
NEW_RECEIPT,
|
||||||
|
RECEIPT_EDITING,
|
||||||
|
UNDEFINED,
|
||||||
|
}
|
||||||
|
|
||||||
|
const ReceiptPage = () => {
|
||||||
|
const [receiptType, setReceiptType] = useState<ReceiptType>(ReceiptType.UNDEFINED);
|
||||||
|
|
||||||
|
const getContent = () => {
|
||||||
|
if (receiptType === ReceiptType.UNDEFINED) {
|
||||||
|
return (
|
||||||
|
<Stack h="95vh">
|
||||||
|
<Button
|
||||||
|
variant="default"
|
||||||
|
onClick={() => setReceiptType(ReceiptType.NEW_RECEIPT)}
|
||||||
|
h="100%"
|
||||||
|
>
|
||||||
|
Начать приемку
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="default"
|
||||||
|
onClick={() => setReceiptType(ReceiptType.RECEIPT_EDITING)}
|
||||||
|
h="100%"
|
||||||
|
>
|
||||||
|
Редактировать паллет/короб
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (receiptType === ReceiptType.NEW_RECEIPT) {
|
||||||
|
return (
|
||||||
|
<ReceiptContextProvider>
|
||||||
|
<Stack>
|
||||||
|
<ReceiptUserSelect />
|
||||||
|
<ReceiptEditor />
|
||||||
|
</Stack>
|
||||||
|
</ReceiptContextProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<ReceiptQrCodeScan />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageBlock noBorderRadius style={{ minHeight: "100vh" }}>
|
||||||
|
{getContent()}
|
||||||
|
</PageBlock>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReceiptPage;
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import { ResidualBoxSchema } from "../../../../client";
|
||||||
|
import { Accordion, ActionIcon, Center, Checkbox, Text, Tooltip } from "@mantine/core";
|
||||||
|
import { IconBox, IconTrash } from "@tabler/icons-react";
|
||||||
|
import ResidualProductsTable from "../ResidualProductsTable/ResidualProductsTable.tsx";
|
||||||
|
import { useResiduesContext } from "../../contexts/ResiduesContext.tsx";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
boxes?: ResidualBoxSchema[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const ResidualBoxes = ({ boxes }: Props) => {
|
||||||
|
const { onDeleteBoxClick, boxIdsToPrint } = useResiduesContext();
|
||||||
|
if (!boxes || boxes.length == 0) return;
|
||||||
|
|
||||||
|
const boxIds = boxes.map((box) => box.id.toString());
|
||||||
|
|
||||||
|
const removeBoxButton = (box: ResidualBoxSchema) => {
|
||||||
|
return (
|
||||||
|
<Tooltip label="Удалить короб">
|
||||||
|
<ActionIcon
|
||||||
|
variant={"default"}
|
||||||
|
onClick={() => onDeleteBoxClick(box)}
|
||||||
|
mx={"md"}
|
||||||
|
>
|
||||||
|
<IconTrash />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkboxToPrint = (box: ResidualBoxSchema) => {
|
||||||
|
return (
|
||||||
|
<Checkbox
|
||||||
|
ml="sm"
|
||||||
|
checked={boxIdsToPrint.has(box.id)}
|
||||||
|
onChange={() => {
|
||||||
|
if (boxIdsToPrint.has(box.id)) {
|
||||||
|
boxIdsToPrint.delete(box.id);
|
||||||
|
} else {
|
||||||
|
boxIdsToPrint.add(box.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderBox = (box: ResidualBoxSchema) => {
|
||||||
|
return (
|
||||||
|
<Accordion.Item key={box.id} value={box.id.toString()}>
|
||||||
|
<Center>
|
||||||
|
<Accordion.Control icon={<IconBox />}>
|
||||||
|
Короб {box.id}
|
||||||
|
</Accordion.Control>
|
||||||
|
{checkboxToPrint(box)}
|
||||||
|
{removeBoxButton(box)}
|
||||||
|
</Center>
|
||||||
|
<Accordion.Panel>
|
||||||
|
{
|
||||||
|
box.residualProducts.length > 0 ? (
|
||||||
|
<ResidualProductsTable
|
||||||
|
items={box.residualProducts.sort((a, b) => a.id - b.id)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Text>Пустой</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</Accordion.Panel>
|
||||||
|
</Accordion.Item>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Accordion
|
||||||
|
multiple={true}
|
||||||
|
defaultValue={boxIds}
|
||||||
|
bd={"solid 1px gray"}
|
||||||
|
>
|
||||||
|
{boxes.sort((a, b) => a.id - b.id).map(box => renderBox(box))}
|
||||||
|
</Accordion>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ResidualBoxes;
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import { BaseTable } from "../../../../components/BaseTable/BaseTable.tsx";
|
||||||
|
import { ResidualProductSchema, ResiduesService } from "../../../../client";
|
||||||
|
import { ActionIcon, Flex, Tooltip } from "@mantine/core";
|
||||||
|
import { IconEdit, IconTrash } from "@tabler/icons-react";
|
||||||
|
import { MRT_TableOptions } from "mantine-react-table";
|
||||||
|
import { notifications } from "../../../../shared/lib/notifications.ts";
|
||||||
|
import { modals } from "@mantine/modals";
|
||||||
|
import { useResiduesContext } from "../../contexts/ResiduesContext.tsx";
|
||||||
|
import useResiduesTableColumns from "../../hooks/residuesTableColumns.tsx";
|
||||||
|
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
items: ResidualProductSchema[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const ResidualProductsTable = ({ items }: Props) => {
|
||||||
|
const columns = useResiduesTableColumns<ResidualProductSchema>();
|
||||||
|
const { selectedClient, refetchClient } = useResiduesContext();
|
||||||
|
|
||||||
|
const onDeleteClick = (residualProduct: ResidualProductSchema) => {
|
||||||
|
ResiduesService.deleteResidualProduct({
|
||||||
|
residualProductId: residualProduct.id,
|
||||||
|
})
|
||||||
|
.then(({ ok, message }) => {
|
||||||
|
notifications.guess(ok, { message });
|
||||||
|
refetchClient();
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err));
|
||||||
|
};
|
||||||
|
|
||||||
|
const onEditClick = (residualProduct: ResidualProductSchema) => {
|
||||||
|
if (!selectedClient) return;
|
||||||
|
modals.openContextModal({
|
||||||
|
modal: "residualProductModal",
|
||||||
|
title: "Редактирование товара на паллете",
|
||||||
|
withCloseButton: false,
|
||||||
|
innerProps: {
|
||||||
|
client: selectedClient,
|
||||||
|
updateOnSubmit: refetchClient,
|
||||||
|
residuesData: {
|
||||||
|
residualProductId: residualProduct.id,
|
||||||
|
product: residualProduct.product,
|
||||||
|
quantity: residualProduct.quantity,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseTable
|
||||||
|
data={items}
|
||||||
|
columns={columns}
|
||||||
|
restProps={
|
||||||
|
{
|
||||||
|
enableSorting: false,
|
||||||
|
enableColumnActions: false,
|
||||||
|
enableRowActions: true,
|
||||||
|
enableRowNumbers: true,
|
||||||
|
positionActionsColumn: "last",
|
||||||
|
renderRowActions: ({ row }) => (
|
||||||
|
<Flex gap="md">
|
||||||
|
<Tooltip label="Удалить">
|
||||||
|
<ActionIcon
|
||||||
|
onClick={() => onDeleteClick(row.original)}
|
||||||
|
variant={"default"}>
|
||||||
|
<IconTrash />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip label="Редактировать">
|
||||||
|
<ActionIcon
|
||||||
|
onClick={() => onEditClick(row.original)}
|
||||||
|
variant={"default"}>
|
||||||
|
<IconEdit />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
</Flex>
|
||||||
|
),
|
||||||
|
} as MRT_TableOptions<ResidualProductSchema>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ResidualProductsTable;
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { Group } from "@mantine/core";
|
||||||
|
import { useResiduesContext } from "../../contexts/ResiduesContext.tsx";
|
||||||
|
import useResiduesPdf from "../../hooks/useResiduesPdf.tsx";
|
||||||
|
import { IconQrcode } from "@tabler/icons-react";
|
||||||
|
import InlineButton from "../../../../components/InlineButton/InlineButton.tsx";
|
||||||
|
|
||||||
|
|
||||||
|
const ResiduesHeader = () => {
|
||||||
|
const {
|
||||||
|
onCreatePalletClick,
|
||||||
|
onCreateBoxClick,
|
||||||
|
} = useResiduesContext();
|
||||||
|
|
||||||
|
const {
|
||||||
|
onGetPalletsPdfClick,
|
||||||
|
} = useResiduesPdf();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group>
|
||||||
|
<InlineButton onClick={() => onCreatePalletClick()}>
|
||||||
|
Добавить паллет
|
||||||
|
</InlineButton>
|
||||||
|
<InlineButton onClick={() => onCreateBoxClick()}>
|
||||||
|
Добавить короб
|
||||||
|
</InlineButton>
|
||||||
|
<InlineButton onClick={() => onGetPalletsPdfClick()}>
|
||||||
|
<IconQrcode />
|
||||||
|
Печать
|
||||||
|
</InlineButton>
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ResiduesHeader;
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { Group, Stack } from "@mantine/core";
|
||||||
|
import ClientSelect from "../../../../components/Selects/ClientSelect/ClientSelect.tsx";
|
||||||
|
import ResiduesHeader from "../ResiduesHeader/ResiduesHeader.tsx";
|
||||||
|
import ResiduesTree from "../ResiduesTree/ResiduesTree.tsx";
|
||||||
|
import { useResiduesContext } from "../../contexts/ResiduesContext.tsx";
|
||||||
|
|
||||||
|
const ResiduesPageContent = () => {
|
||||||
|
const { selectedClient, selectClient } = useResiduesContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack h="92vh">
|
||||||
|
<Group>
|
||||||
|
<ClientSelect onChange={selectClient} />
|
||||||
|
{selectedClient && <ResiduesHeader />}
|
||||||
|
</Group>
|
||||||
|
{selectedClient && <ResiduesTree />}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ResiduesPageContent;
|
||||||
135
src/pages/ResiduesPage/components/ResiduesTree/ResiduesTree.tsx
Normal file
135
src/pages/ResiduesPage/components/ResiduesTree/ResiduesTree.tsx
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
ActionIcon,
|
||||||
|
Button,
|
||||||
|
Center, Checkbox,
|
||||||
|
Group,
|
||||||
|
rem,
|
||||||
|
ScrollArea,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from "@mantine/core";
|
||||||
|
import { IconPlus, IconSpace, IconTrash } from "@tabler/icons-react";
|
||||||
|
import { useResiduesContext } from "../../contexts/ResiduesContext.tsx";
|
||||||
|
import { ResidualBoxSchema, ResidualPalletSchema, ResidualProductSchema } from "../../../../client";
|
||||||
|
import ResidualProductsTable from "../ResidualProductsTable/ResidualProductsTable.tsx";
|
||||||
|
import ResidualBoxes from "../ResidualBoxes/ResidualBoxes.tsx";
|
||||||
|
import residualBoxes from "../ResidualBoxes/ResidualBoxes.tsx";
|
||||||
|
|
||||||
|
const ResiduesTree = () => {
|
||||||
|
const {
|
||||||
|
selectedClient,
|
||||||
|
onDeletePalletClick,
|
||||||
|
onCreateBoxClick,
|
||||||
|
palletIdsToPrint,
|
||||||
|
} = useResiduesContext();
|
||||||
|
const palletIds: string[] = [];
|
||||||
|
|
||||||
|
const sortById = (data?: ResidualPalletSchema[] | ResidualBoxSchema[] | ResidualProductSchema[]) => {
|
||||||
|
return data?.sort((a, b) => a.id - b.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkboxToPrint = (pallet: ResidualPalletSchema) => {
|
||||||
|
return (
|
||||||
|
<Checkbox
|
||||||
|
ml="sm"
|
||||||
|
checked={palletIdsToPrint.has(pallet.id)}
|
||||||
|
onChange={() => {
|
||||||
|
if (palletIdsToPrint.has(pallet.id)) {
|
||||||
|
palletIdsToPrint.delete(pallet.id);
|
||||||
|
} else {
|
||||||
|
palletIdsToPrint.add(pallet.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPallets = () => {
|
||||||
|
const sortedPallets = sortById(selectedClient?.pallets) as ResidualPalletSchema[];
|
||||||
|
return sortedPallets?.map((pallet => {
|
||||||
|
palletIds.push(pallet.id.toString());
|
||||||
|
return (
|
||||||
|
<Accordion.Item key={pallet.id} value={pallet.id.toString()}>
|
||||||
|
<Center>
|
||||||
|
<Accordion.Control icon={<IconSpace />}>
|
||||||
|
Паллет - П{pallet.id}
|
||||||
|
</Accordion.Control>
|
||||||
|
{checkboxToPrint(pallet)}
|
||||||
|
{removePalletButton(pallet)}
|
||||||
|
</Center>
|
||||||
|
<Accordion.Panel>
|
||||||
|
{getPalletContent(pallet)}
|
||||||
|
</Accordion.Panel>
|
||||||
|
</Accordion.Item>
|
||||||
|
);
|
||||||
|
})) ?? [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const removePalletButton = (pallet: ResidualPalletSchema) => {
|
||||||
|
return (
|
||||||
|
<Tooltip label="Удалить паллет">
|
||||||
|
<ActionIcon
|
||||||
|
variant={"default"}
|
||||||
|
onClick={() => onDeletePalletClick(pallet)}
|
||||||
|
mx={"md"}
|
||||||
|
>
|
||||||
|
<IconTrash />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const createBoxButton = (palletId: number) => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant={"default"}
|
||||||
|
onClick={() => onCreateBoxClick(palletId)}
|
||||||
|
>
|
||||||
|
<Group gap={"md"}>
|
||||||
|
<IconPlus />
|
||||||
|
Добавить короб
|
||||||
|
</Group>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPalletContent = (pallet: ResidualPalletSchema) => {
|
||||||
|
const isEmpty = pallet.boxes.length === 0 && pallet.residualProducts.length === 0;
|
||||||
|
const isBox = pallet.residualProducts.length === 0;
|
||||||
|
const title = isEmpty ? "Пустой" : isBox ? "Короба" : "Товары";
|
||||||
|
|
||||||
|
const residualProducts = sortById(pallet.residualProducts) as ResidualProductSchema[];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack gap={rem(5)}>
|
||||||
|
<Group justify={"space-between"}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Text>Дата добавления: {new Date(pallet.createdAt).toLocaleString("ru-RU")}</Text>
|
||||||
|
<Title order={6}>{title}</Title>
|
||||||
|
</Stack>
|
||||||
|
{isBox && createBoxButton(pallet.id)}
|
||||||
|
</Group>
|
||||||
|
{residualProducts.length > 0 && <ResidualProductsTable items={residualProducts} />}
|
||||||
|
{residualBoxes.length > 0 && <ResidualBoxes boxes={pallet.boxes} />}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollArea>
|
||||||
|
<Accordion
|
||||||
|
multiple={true}
|
||||||
|
defaultValue={palletIds}
|
||||||
|
bd={"solid 1px gray"}
|
||||||
|
>
|
||||||
|
<ResidualBoxes boxes={selectedClient?.boxes} />
|
||||||
|
{getPallets()}
|
||||||
|
</Accordion>
|
||||||
|
</ScrollArea>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ResiduesTree;
|
||||||
176
src/pages/ResiduesPage/contexts/ResiduesContext.tsx
Normal file
176
src/pages/ResiduesPage/contexts/ResiduesContext.tsx
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
import { createContext, FC, useContext, useState } from "react";
|
||||||
|
import {
|
||||||
|
ClientDetailedSchema,
|
||||||
|
ClientSchema,
|
||||||
|
ClientService,
|
||||||
|
ResidualBoxSchema,
|
||||||
|
ResidualPalletSchema,
|
||||||
|
ResiduesService,
|
||||||
|
} from "../../../client";
|
||||||
|
import { modals } from "@mantine/modals";
|
||||||
|
import { Text } from "@mantine/core";
|
||||||
|
import { notifications } from "../../../shared/lib/notifications.ts";
|
||||||
|
import { useSet } from "@mantine/hooks";
|
||||||
|
|
||||||
|
type ResiduesContextState = {
|
||||||
|
selectedClient?: ClientDetailedSchema;
|
||||||
|
selectClient: (client?: ClientSchema) => void;
|
||||||
|
refetchClient: () => void;
|
||||||
|
onDeletePalletClick: (pallet: ResidualPalletSchema) => void;
|
||||||
|
onCreatePalletClick: () => void;
|
||||||
|
onDeleteBoxClick: (box: ResidualBoxSchema) => void;
|
||||||
|
onCreateBoxClick: (pallet?: number) => void;
|
||||||
|
boxIdsToPrint: Set<number>;
|
||||||
|
palletIdsToPrint: Set<number>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ResiduesContext = createContext<ResiduesContextState | undefined>(
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
const useResiduesContextState = () => {
|
||||||
|
const [selectedClient, setSelectedClient] = useState<ClientDetailedSchema>();
|
||||||
|
const boxIdsToPrint = useSet<number>();
|
||||||
|
const palletIdsToPrint = useSet<number>();
|
||||||
|
|
||||||
|
const fetchClient = (clientId?: number) => {
|
||||||
|
if (!clientId) {
|
||||||
|
setSelectedClient(undefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientService.getClient({
|
||||||
|
clientId,
|
||||||
|
}).then(res => {
|
||||||
|
setSelectedClient(res.client);
|
||||||
|
}).catch(err => console.log(err));
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectClient = (client?: ClientSchema) => {
|
||||||
|
fetchClient(client?.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const refetchClient = () => {
|
||||||
|
fetchClient(selectedClient?.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDeletePallet = (palletId: number) => {
|
||||||
|
ResiduesService.deleteResidualPallet({ palletId })
|
||||||
|
.then(({ ok, message }) => {
|
||||||
|
if (!ok) {
|
||||||
|
notifications.error({ message });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
palletIdsToPrint.delete(palletId);
|
||||||
|
refetchClient();
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err));
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDeletePalletClick = (pallet: ResidualPalletSchema) => {
|
||||||
|
if (!selectedClient) return;
|
||||||
|
if (pallet.boxes.length === 0 && pallet.residualProducts.length === 0) {
|
||||||
|
onDeletePallet(pallet.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
modals.openConfirmModal({
|
||||||
|
title: "Удаление паллета",
|
||||||
|
children: <Text size="sm">Вы уверены что хотите удалить паллет?</Text>,
|
||||||
|
labels: { confirm: "Да", cancel: "Нет" },
|
||||||
|
confirmProps: { color: "red" },
|
||||||
|
onConfirm: () => onDeletePallet(pallet.id),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDeleteBox = (boxId: number) => {
|
||||||
|
ResiduesService.deleteResidualBox({ boxId })
|
||||||
|
.then(({ ok, message }) => {
|
||||||
|
if (!ok) {
|
||||||
|
notifications.error({ message });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
boxIdsToPrint.delete(boxId);
|
||||||
|
refetchClient();
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err));
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDeleteBoxClick = (box: ResidualBoxSchema) => {
|
||||||
|
if (!selectedClient) return;
|
||||||
|
if (box.residualProducts.length === 0) {
|
||||||
|
onDeleteBox(box.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
modals.openConfirmModal({
|
||||||
|
title: "Удаление короба",
|
||||||
|
children: <Text size="sm">Вы уверены что хотите удалить короб?</Text>,
|
||||||
|
labels: { confirm: "Да", cancel: "Нет" },
|
||||||
|
confirmProps: { color: "red" },
|
||||||
|
onConfirm: () => onDeleteBox(box.id),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCreatePalletClick = () => {
|
||||||
|
if (!selectedClient) return;
|
||||||
|
ResiduesService.createResidualPallet({
|
||||||
|
requestBody: { clientId: selectedClient.id },
|
||||||
|
})
|
||||||
|
.then(({ ok, message }) => {
|
||||||
|
if (!ok) notifications.error({ message });
|
||||||
|
refetchClient();
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err));
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCreateBoxClick = (palletId?: number) => {
|
||||||
|
if (!selectedClient) return;
|
||||||
|
ResiduesService.createResidualBox({
|
||||||
|
requestBody: {
|
||||||
|
palletId: palletId ?? null,
|
||||||
|
clientId: palletId ? null : selectedClient.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(({ ok, message }) => {
|
||||||
|
if (!ok) notifications.error({ message });
|
||||||
|
refetchClient();
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err));
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
selectedClient,
|
||||||
|
selectClient,
|
||||||
|
refetchClient,
|
||||||
|
onDeletePalletClick,
|
||||||
|
onCreatePalletClick,
|
||||||
|
onDeleteBoxClick,
|
||||||
|
onCreateBoxClick,
|
||||||
|
boxIdsToPrint,
|
||||||
|
palletIdsToPrint,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type ResiduesContextProviderProps = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ResiduesContextProvider: FC<ResiduesContextProviderProps> = ({ children }) => {
|
||||||
|
const state = useResiduesContextState();
|
||||||
|
return (
|
||||||
|
<ResiduesContext.Provider value={state}>
|
||||||
|
{children}
|
||||||
|
</ResiduesContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useResiduesContext = () => {
|
||||||
|
const context = useContext(ResiduesContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error(
|
||||||
|
"useResiduesContext must be used within a ResiduesContextProvider",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
32
src/pages/ResiduesPage/hooks/residuesTableColumns.tsx
Normal file
32
src/pages/ResiduesPage/hooks/residuesTableColumns.tsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { useMemo } from "react";
|
||||||
|
import { MRT_ColumnDef, MRT_RowData } from "mantine-react-table";
|
||||||
|
|
||||||
|
|
||||||
|
const useResiduesTableColumns = <T extends MRT_RowData>() => {
|
||||||
|
return useMemo<MRT_ColumnDef<T>[]>(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
header: "Название",
|
||||||
|
accessorKey: "product.name",
|
||||||
|
Cell: ({ row }) => row.original.product?.name ?? "-",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "Артикул",
|
||||||
|
accessorKey: "product.article",
|
||||||
|
Cell: ({ row }) => row.original.product?.article ?? "-",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "Размер",
|
||||||
|
accessorKey: "product.size",
|
||||||
|
Cell: ({ row }) => row.original.product?.size ?? "-",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "Количество",
|
||||||
|
accessorKey: "quantity",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useResiduesTableColumns;
|
||||||
31
src/pages/ResiduesPage/hooks/useResiduesPdf.tsx
Normal file
31
src/pages/ResiduesPage/hooks/useResiduesPdf.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { useResiduesContext } from "../contexts/ResiduesContext.tsx";
|
||||||
|
import { notifications } from "../../../shared/lib/notifications.ts";
|
||||||
|
|
||||||
|
const useResiduesPdf = () => {
|
||||||
|
const { palletIdsToPrint, boxIdsToPrint } = useResiduesContext();
|
||||||
|
|
||||||
|
const basePdfUrl = `${import.meta.env.VITE_API_URL}/residues/pdf`;
|
||||||
|
|
||||||
|
const getPdf = (url: string) => {
|
||||||
|
const pdfWindow = window.open(url);
|
||||||
|
if (!pdfWindow) return;
|
||||||
|
pdfWindow.print();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onGetPalletsPdfClick = () => {
|
||||||
|
if (palletIdsToPrint.size === 0 && boxIdsToPrint.size === 0) {
|
||||||
|
notifications.show({ message: "Не выбран ни один элемент для печати "});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const palletIdsStr = palletIdsToPrint.values().toArray().join(",")
|
||||||
|
const boxIdsStr = boxIdsToPrint.values().toArray().join(",")
|
||||||
|
getPdf(`${basePdfUrl}/?pallet_ids=${palletIdsStr}&box_ids=${boxIdsStr}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
onGetPalletsPdfClick,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useResiduesPdf;
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import { useForm } from "@mantine/form";
|
||||||
|
import { ContextModalProps } from "@mantine/modals";
|
||||||
|
import { Button, Flex, NumberInput, rem } from "@mantine/core";
|
||||||
|
import { ClientDetailedSchema, ProductSchema, ResiduesService, UpdateResidualProductSchema } from "../../../../client";
|
||||||
|
import { ResidualModalForm, UpdateResidualProductData } from "../../types/ResidualProductData.tsx";
|
||||||
|
import { notifications } from "../../../../shared/lib/notifications.ts";
|
||||||
|
import ProductSelect from "../../../../components/ProductSelect/ProductSelect.tsx";
|
||||||
|
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
updateOnSubmit: () => void;
|
||||||
|
client: ClientDetailedSchema;
|
||||||
|
residuesData: UpdateResidualProductData;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ResidualProductModal = ({
|
||||||
|
context,
|
||||||
|
id,
|
||||||
|
innerProps,
|
||||||
|
}: ContextModalProps<Props>) => {
|
||||||
|
const initialValues: ResidualModalForm = {
|
||||||
|
quantity: innerProps.residuesData.quantity ?? 0,
|
||||||
|
product: innerProps.residuesData.product,
|
||||||
|
};
|
||||||
|
const form = useForm<ResidualModalForm>({
|
||||||
|
initialValues,
|
||||||
|
validate: {
|
||||||
|
product: product => !product && "Необходимо выбрать товар",
|
||||||
|
quantity: quantity => quantity === 0 && "Слишком мало товара",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateResidualProduct = () => {
|
||||||
|
const data = {
|
||||||
|
...form.values,
|
||||||
|
productId: form.values.product!.id,
|
||||||
|
} as UpdateResidualProductSchema;
|
||||||
|
|
||||||
|
ResiduesService.updateResidualProduct({
|
||||||
|
requestBody: { data },
|
||||||
|
residualProductId: innerProps.residuesData.residualProductId,
|
||||||
|
})
|
||||||
|
.then(({ ok, message }) => {
|
||||||
|
notifications.guess(ok, { message: message });
|
||||||
|
innerProps.updateOnSubmit();
|
||||||
|
if (ok) context.closeContextModal(id);
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={form.onSubmit(() => updateResidualProduct())}>
|
||||||
|
<Flex
|
||||||
|
direction={"column"}
|
||||||
|
gap={rem(10)}
|
||||||
|
>
|
||||||
|
<ProductSelect
|
||||||
|
label={"Товар"}
|
||||||
|
placeholder={"Выберите товар"}
|
||||||
|
{...form.getInputProps("product")}
|
||||||
|
defaultValue={innerProps.residuesData.product as (ProductSchema & string)}
|
||||||
|
clientId={innerProps.client.id}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<NumberInput
|
||||||
|
label={"Количество"}
|
||||||
|
hideControls
|
||||||
|
{...form.getInputProps("quantity")}
|
||||||
|
min={0}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant={"default"}
|
||||||
|
type={"submit"}
|
||||||
|
>
|
||||||
|
Сохранить
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ResidualProductModal;
|
||||||
12
src/pages/ResiduesPage/types/ResidualProductData.tsx
Normal file
12
src/pages/ResiduesPage/types/ResidualProductData.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { ProductSchema } from "../../../client";
|
||||||
|
|
||||||
|
export type ResidualModalForm = {
|
||||||
|
quantity: number;
|
||||||
|
product: ProductSchema | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UpdateResidualProductData = {
|
||||||
|
product: ProductSchema | null;
|
||||||
|
quantity: number;
|
||||||
|
residualProductId: number;
|
||||||
|
};
|
||||||
19
src/pages/ResiduesPage/ui/ResiduesPage.tsx
Normal file
19
src/pages/ResiduesPage/ui/ResiduesPage.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import styles from "../../ProductsPage/ui/ProductsPage.module.css";
|
||||||
|
import PageBlock from "../../../components/PageBlock/PageBlock.tsx";
|
||||||
|
import { ResiduesContextProvider } from "../contexts/ResiduesContext.tsx";
|
||||||
|
import ResiduesPageContent from "../components/ResiduesPageContent/ResiduesPageContent.tsx";
|
||||||
|
|
||||||
|
|
||||||
|
const ResiduesPage = () => {
|
||||||
|
return (
|
||||||
|
<div className={styles["container"]}>
|
||||||
|
<PageBlock fullHeight>
|
||||||
|
<ResiduesContextProvider>
|
||||||
|
<ResiduesPageContent />
|
||||||
|
</ResiduesContextProvider>
|
||||||
|
</PageBlock>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ResiduesPage;
|
||||||
6
src/routes/receipt.lazy.tsx
Normal file
6
src/routes/receipt.lazy.tsx
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { createLazyFileRoute } from '@tanstack/react-router'
|
||||||
|
import ReceiptPage from "../pages/ReceiptPage/ui/ReceiptPage.tsx";
|
||||||
|
|
||||||
|
export const Route = createLazyFileRoute('/receipt')({
|
||||||
|
component: () => <ReceiptPage />,
|
||||||
|
})
|
||||||
6
src/routes/residues.lazy.tsx
Normal file
6
src/routes/residues.lazy.tsx
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
|
import ResiduesPage from "../pages/ResiduesPage/ui/ResiduesPage.tsx";
|
||||||
|
|
||||||
|
export const Route = createLazyFileRoute("/residues")({
|
||||||
|
component: () => <ResiduesPage />,
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user