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