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 { ClientDeleteRequest } from './models/ClientDeleteRequest';
|
||||
export type { ClientDeleteResponse } from './models/ClientDeleteResponse';
|
||||
export type { ClientDetailedSchema } from './models/ClientDetailedSchema';
|
||||
export type { ClientDetailsSchema } from './models/ClientDetailsSchema';
|
||||
export type { ClientGetAllResponse } from './models/ClientGetAllResponse';
|
||||
export type { ClientGetResponse } from './models/ClientGetResponse';
|
||||
export type { ClientSchema } from './models/ClientSchema';
|
||||
export type { ClientUpdateDetailsRequest } from './models/ClientUpdateDetailsRequest';
|
||||
export type { ClientUpdateRequest } from './models/ClientUpdateRequest';
|
||||
@@ -71,6 +73,13 @@ export type { CreatePositionRequest } from './models/CreatePositionRequest';
|
||||
export type { CreatePositionResponse } from './models/CreatePositionResponse';
|
||||
export type { CreatePriceCategoryRequest } from './models/CreatePriceCategoryRequest';
|
||||
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 { CreateServicesKitRequest } from './models/CreateServicesKitRequest';
|
||||
export type { CreateServicesKitResponse } from './models/CreateServicesKitResponse';
|
||||
@@ -163,6 +172,9 @@ export type { DeletePositionRequest } from './models/DeletePositionRequest';
|
||||
export type { DeletePositionResponse } from './models/DeletePositionResponse';
|
||||
export type { DeletePriceCategoryRequest } from './models/DeletePriceCategoryRequest';
|
||||
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 { DeleteShippingProductResponse } from './models/DeleteShippingProductResponse';
|
||||
export type { DeleteShippingWarehouseRequest } from './models/DeleteShippingWarehouseRequest';
|
||||
@@ -214,6 +226,8 @@ export type { GetProfitChartDataRequest } from './models/GetProfitChartDataReque
|
||||
export type { GetProfitChartDataResponse } from './models/GetProfitChartDataResponse';
|
||||
export type { GetProfitTableDataRequest } from './models/GetProfitTableDataRequest';
|
||||
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 { GetTimeTrackingRecordsRequest } from './models/GetTimeTrackingRecordsRequest';
|
||||
export type { GetTimeTrackingRecordsResponse } from './models/GetTimeTrackingRecordsResponse';
|
||||
@@ -221,6 +235,8 @@ export type { GetTransactionTagsResponse } from './models/GetTransactionTagsResp
|
||||
export type { GetWorkShiftsResponse } from './models/GetWorkShiftsResponse';
|
||||
export type { GroupBillRequestSchema } from './models/GroupBillRequestSchema';
|
||||
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 { ManageEmployeeResponse } from './models/ManageEmployeeResponse';
|
||||
export type { MarketplaceCreateSchema } from './models/MarketplaceCreateSchema';
|
||||
@@ -242,6 +258,7 @@ export type { PermissionSchema } from './models/PermissionSchema';
|
||||
export type { PositionSchema } from './models/PositionSchema';
|
||||
export type { ProductAddBarcodeRequest } from './models/ProductAddBarcodeRequest';
|
||||
export type { ProductAddBarcodeResponse } from './models/ProductAddBarcodeResponse';
|
||||
export type { ProductAndQuantitySchema } from './models/ProductAndQuantitySchema';
|
||||
export type { ProductCreateRequest } from './models/ProductCreateRequest';
|
||||
export type { ProductCreateResponse } from './models/ProductCreateResponse';
|
||||
export type { ProductDeleteBarcodeImageResponse } from './models/ProductDeleteBarcodeImageResponse';
|
||||
@@ -262,6 +279,11 @@ export type { ProductUploadImageResponse } from './models/ProductUploadImageResp
|
||||
export type { ProfitChartDataItem } from './models/ProfitChartDataItem';
|
||||
export type { ProfitTableDataItem } from './models/ProfitTableDataItem';
|
||||
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 { ServiceCategoryPriceSchema } from './models/ServiceCategoryPriceSchema';
|
||||
export type { ServiceCategoryReorderRequest } from './models/ServiceCategoryReorderRequest';
|
||||
@@ -310,6 +332,9 @@ export type { UpdatePayRateRequest } from './models/UpdatePayRateRequest';
|
||||
export type { UpdatePayRateResponse } from './models/UpdatePayRateResponse';
|
||||
export type { UpdatePriceCategoryRequest } from './models/UpdatePriceCategoryRequest';
|
||||
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 { UpdateServicesKitRequest } from './models/UpdateServicesKitRequest';
|
||||
export type { UpdateServicesKitResponse } from './models/UpdateServicesKitResponse';
|
||||
@@ -345,6 +370,7 @@ export { MarketplaceService } from './services/MarketplaceService';
|
||||
export { PayrollService } from './services/PayrollService';
|
||||
export { PositionService } from './services/PositionService';
|
||||
export { ProductService } from './services/ProductService';
|
||||
export { ResiduesService } from './services/ResiduesService';
|
||||
export { RoleService } from './services/RoleService';
|
||||
export { ServiceService } from './services/ServiceService';
|
||||
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;
|
||||
isDeleted: boolean;
|
||||
isCompleted: boolean;
|
||||
isAccounted: boolean;
|
||||
comment: string;
|
||||
shippingWarehouse?: (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 { ClientDeleteResponse } from '../models/ClientDeleteResponse';
|
||||
import type { ClientGetAllResponse } from '../models/ClientGetAllResponse';
|
||||
import type { ClientGetResponse } from '../models/ClientGetResponse';
|
||||
import type { ClientUpdateDetailsRequest } from '../models/ClientUpdateDetailsRequest';
|
||||
import type { ClientUpdateRequest } from '../models/ClientUpdateRequest';
|
||||
import type { ClientUpdateResponse } from '../models/ClientUpdateResponse';
|
||||
@@ -66,6 +67,27 @@ export class ClientService {
|
||||
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
|
||||
* @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,
|
||||
IconDashboard,
|
||||
IconFileBarcode,
|
||||
IconHome2,
|
||||
IconHome2, IconHomeEdit,
|
||||
IconLogout,
|
||||
IconMan,
|
||||
IconMoon,
|
||||
@@ -94,6 +94,11 @@ const mockdata = [
|
||||
label: "Маркетплейсы",
|
||||
href: "/marketplaces",
|
||||
},
|
||||
{
|
||||
icon: IconHomeEdit,
|
||||
label: "Остатки",
|
||||
href: "/residues",
|
||||
},
|
||||
];
|
||||
|
||||
const adminMockdata = [
|
||||
|
||||
@@ -27,3 +27,7 @@
|
||||
.container-full-height-fixed {
|
||||
height: calc(100vh - (rem(20) * 2));
|
||||
}
|
||||
|
||||
.container-no-border-radius {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ type Props = {
|
||||
style?: CSSProperties;
|
||||
fullHeight?: boolean;
|
||||
fullHeightFixed?: boolean;
|
||||
noBorderRadius?: boolean;
|
||||
};
|
||||
export const PageBlock: FC<Props> = ({
|
||||
children,
|
||||
@@ -15,6 +16,7 @@ export const PageBlock: FC<Props> = ({
|
||||
fluid = true,
|
||||
fullHeight = false,
|
||||
fullHeightFixed = false,
|
||||
noBorderRadius = false,
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
@@ -23,7 +25,8 @@ export const PageBlock: FC<Props> = ({
|
||||
styles["container"],
|
||||
fluid && styles["container-fluid"],
|
||||
fullHeight && styles["container-full-height"],
|
||||
fullHeightFixed && styles["container-full-height-fixed"]
|
||||
fullHeightFixed && styles["container-full-height-fixed"],
|
||||
noBorderRadius && styles["container-no-border-radius"]
|
||||
)}>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,8 @@ import AddDealServiceModal from "../pages/LeadsPage/modals/AddDealServiceModal.t
|
||||
import AddDealProductModal from "../pages/LeadsPage/modals/AddDealProductModal.tsx";
|
||||
import PrintBarcodeModal from "./PrintBarcodeModal/PrintBarcodeModal.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 UserFormModal from "../pages/AdminPage/modals/UserFormModal/UserFormModal.tsx";
|
||||
import EmployeeSelectModal from "./EmployeeSelectModal/EmployeeSelectModal.tsx";
|
||||
@@ -29,6 +30,9 @@ import DepartmentModal from "../pages/AdminPage/tabs/OrganizationalStructureTab/
|
||||
import AddUserToDepartmentModal
|
||||
from "../pages/AdminPage/tabs/OrganizationalStructureTab/modals/AddUserToDepartmentModal.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 = {
|
||||
enterDeadline: EnterDeadlineModal,
|
||||
@@ -58,6 +62,9 @@ export const modals = {
|
||||
transactionFormModal: TransactionFormModal,
|
||||
transactionTagsModal: TransactionTagsModal,
|
||||
shippingProductModal: ShippingProductModal,
|
||||
residualProductModal: ResidualProductModal,
|
||||
newReceiptModal: NewReceiptModal,
|
||||
receiptModal: ReceiptModal,
|
||||
departmentModal: DepartmentModal,
|
||||
addUserToDepartmentModal: AddUserToDepartmentModal,
|
||||
assignUserModal: AssignUserModal,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
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 useShippingQrs from "./hooks/useShippingQrs.tsx";
|
||||
import { IconPrinter, IconQrcode } from "@tabler/icons-react";
|
||||
import { ReactNode } from "react";
|
||||
import InlineButton from "../../../../components/InlineButton/InlineButton.tsx";
|
||||
|
||||
|
||||
const ShippingTab = () => {
|
||||
@@ -18,28 +18,27 @@ const ShippingTab = () => {
|
||||
onGetBoxesPdfClick,
|
||||
} = useShippingQrs();
|
||||
|
||||
const getButton = (label: string, func: () => void, icon?: ReactNode) => {
|
||||
return (
|
||||
<Button
|
||||
variant={"default"}
|
||||
onClick={func}
|
||||
>
|
||||
<Group gap={rem(10)}>
|
||||
{icon}
|
||||
{label}
|
||||
</Group>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack h={"94vh"}>
|
||||
<Group>
|
||||
{getButton("Добавить паллет", onCreatePalletClick)}
|
||||
{getButton("Добавить короб", onCreateBoxInDealClick)}
|
||||
{getButton("Сделка", onGetDealQrPdfClick, <IconQrcode />)}
|
||||
{getButton("Паллеты", onGetPalletsPdfClick, <IconPrinter />)}
|
||||
{getButton("Короба", onGetBoxesPdfClick, <IconPrinter />)}
|
||||
<InlineButton onClick={() => onCreatePalletClick()}>
|
||||
Добавить паллет
|
||||
</InlineButton>
|
||||
<InlineButton onClick={() => onCreateBoxInDealClick()}>
|
||||
Добавить короб
|
||||
</InlineButton>
|
||||
<InlineButton onClick={() => onGetDealQrPdfClick()}>
|
||||
<IconQrcode />
|
||||
Сделка
|
||||
</InlineButton>
|
||||
<InlineButton onClick={() => onGetPalletsPdfClick()}>
|
||||
<IconPrinter />
|
||||
Паллеты
|
||||
</InlineButton>
|
||||
<InlineButton onClick={() => onGetBoxesPdfClick()}>
|
||||
<IconPrinter />
|
||||
Короба
|
||||
</InlineButton>
|
||||
</Group>
|
||||
<ScrollArea>
|
||||
<ShippingTree />
|
||||
|
||||
@@ -9,4 +9,8 @@
|
||||
|
||||
.container {
|
||||
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 authState = useSelector((state: RootState) => state.auth);
|
||||
const uiState = useSelector((state: RootState) => state.ui);
|
||||
const navbarNeeded = authState.isAuthorized && !authState.isGuest;
|
||||
|
||||
return (
|
||||
<AppShell
|
||||
layout={"alt"}
|
||||
withBorder={false}
|
||||
navbar={
|
||||
authState.isAuthorized && !authState.isGuest
|
||||
navbarNeeded
|
||||
? {
|
||||
width: "130px",
|
||||
breakpoint: "sm",
|
||||
collapsed: { desktop: uiState.hideNavbar },
|
||||
collapsed: { desktop: uiState.hideNavbar, mobile: true },
|
||||
}
|
||||
: undefined
|
||||
|
||||
}>
|
||||
<AppShell.Header>
|
||||
|
||||
</AppShell.Header>
|
||||
<AppShell.Navbar>
|
||||
{authState.isAuthorized && !authState.isGuest && (
|
||||
{navbarNeeded && (
|
||||
<Flex
|
||||
className={styles["main-container"]}
|
||||
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