From cc1877fd395f57479fb368bd6342e21ded4f3c59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?LI-CCONG=5C=E6=9D=8E=E8=81=AA=E8=81=AA?= <1441652193@qq.com> Date: Sat, 2 Mar 2024 19:52:50 +0800 Subject: [PATCH] =?UTF-8?q?=E9=9D=99=E6=80=81=E8=B5=84=E6=BA=90=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E3=80=81=E6=95=A3=E6=88=B7=E7=AB=AF=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E9=97=AE=E9=A2=98=E4=BF=AE=E5=A4=8D=E3=80=81=E8=AE=A2=E5=8D=95?= =?UTF-8?q?=E4=B8=9A=E5=8A=A1=E5=8A=9F=E8=83=BD=E5=BC=80=E5=8F=91v2?= =?UTF-8?q?=E3=80=81swagger=E6=9E=9A=E4=B8=BE=E8=BD=AC=E6=8D=A2=E5=99=A8?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/advice/CommonExceptionAdvice.java | 62 +++++- .../cc/yunxi/common/config/JsonConfig.java | 12 +- .../common/config/SwaggerEnumPlugin.java | 186 ++++++++++++++++++ .../java/cc/yunxi/common/domain/PageDTO.java | 1 + .../cc/yunxi/common/domain/PageQuery.java | 1 + .../domain/convert/EnumDeserializer.java | 65 ++++++ .../common/domain/convert/EnumSerializer.java | 39 ++++ .../LocalDateTimeDeserializer.java | 4 +- .../LocalDateTimeSerializer.java | 2 +- .../common/domain/{ => function}/Convert.java | 3 +- .../domain/{ => function}/SFunction.java | 2 +- .../java/cc/yunxi/common/enums/BaseEnum.java | 11 ++ .../common/enums/SwaggerDisplayEnum.java | 16 ++ .../StringCodeToEnumConverterFactory.java | 71 +++++++ .../java/cc/yunxi/common/utils/BeanUtils.java | 2 +- .../cc/yunxi/common/utils/LambdaUtil.java | 2 +- .../main/java/cc/yunxi/NxhsApplication.java | 2 +- .../java/cc/yunxi/aspect/JudgeUserAspect.java | 2 +- .../java/cc/yunxi/config/WebMvcConfig.java | 83 +++++++- .../java/cc/yunxi/config/WxShProperties.java | 17 ++ .../cc/yunxi/controller/ClientController.java | 2 + .../cc/yunxi/controller/CommonController.java | 50 ++--- .../controller/RecycleOrderController.java | 40 +++- .../cc/yunxi/controller/TestController.java | 14 +- .../java/cc/yunxi/domain/dto/UserDTO.java | 4 +- .../java/cc/yunxi/domain/dto/WxLoginDTO.java | 2 +- .../main/java/cc/yunxi/domain/po/Client.java | 6 +- .../java/cc/yunxi/domain/po/RecycleOrder.java | 4 +- .../java/cc/yunxi/domain/po/Recycler.java | 2 + .../cc/yunxi/domain/query/ClientQuery.java | 2 +- .../yunxi/domain/query/RecycleOrderQuery.java | 4 +- .../cc/yunxi/domain/query/RecyclerQuery.java | 2 +- .../java/cc/yunxi/domain/query/TestQuery.java | 14 ++ .../vo/recycleorder/RecycleOrderCreateVO.java | 30 ++- .../vo/recycleorder/RecycleOrderRespVO.java | 4 +- .../vo/recycleorder/RecycleOrderTakingVO.java | 21 ++ .../vo/recycleorder/RecycleOrderUpdateVO.java | 37 ++++ .../java/cc/yunxi/enums/BusinessCodeEnum.java | 3 +- .../java/cc/yunxi/enums/OrderStatusEnum.java | 7 +- .../java/cc/yunxi/enums/OrderTypeEnum.java | 6 +- .../java/cc/yunxi/enums/UserTypeEnum.java | 16 +- .../java/cc/yunxi/service/IClientService.java | 9 + .../java/cc/yunxi/service/ICommonService.java | 5 +- .../yunxi/service/IRecycleOrderService.java | 26 ++- .../yunxi/service/impl/ClientServiceImpl.java | 18 +- .../cc/yunxi/service/impl/CommonService.java | 81 ++++++-- .../service/impl/RecycleOrderServiceImpl.java | 60 ++++++ .../src/main/resources/application.yml | 11 +- nxhs-service/src/main/resources/static/bg.png | Bin 0 -> 360677 bytes .../java/cc/yunxi/NxhsApplicationTest.java | 8 +- 50 files changed, 928 insertions(+), 143 deletions(-) create mode 100644 nxhs-common/src/main/java/cc/yunxi/common/config/SwaggerEnumPlugin.java create mode 100644 nxhs-common/src/main/java/cc/yunxi/common/domain/convert/EnumDeserializer.java create mode 100644 nxhs-common/src/main/java/cc/yunxi/common/domain/convert/EnumSerializer.java rename nxhs-common/src/main/java/cc/yunxi/common/domain/{ => convert}/LocalDateTimeDeserializer.java (88%) rename nxhs-common/src/main/java/cc/yunxi/common/domain/{ => convert}/LocalDateTimeSerializer.java (96%) rename nxhs-common/src/main/java/cc/yunxi/common/domain/{ => function}/Convert.java (68%) rename nxhs-common/src/main/java/cc/yunxi/common/domain/{ => function}/SFunction.java (80%) create mode 100644 nxhs-common/src/main/java/cc/yunxi/common/enums/BaseEnum.java create mode 100644 nxhs-common/src/main/java/cc/yunxi/common/enums/SwaggerDisplayEnum.java create mode 100644 nxhs-common/src/main/java/cc/yunxi/common/factory/StringCodeToEnumConverterFactory.java create mode 100644 nxhs-service/src/main/java/cc/yunxi/config/WxShProperties.java create mode 100644 nxhs-service/src/main/java/cc/yunxi/domain/query/TestQuery.java create mode 100644 nxhs-service/src/main/java/cc/yunxi/domain/vo/recycleorder/RecycleOrderTakingVO.java create mode 100644 nxhs-service/src/main/java/cc/yunxi/domain/vo/recycleorder/RecycleOrderUpdateVO.java create mode 100644 nxhs-service/src/main/resources/static/bg.png diff --git a/nxhs-common/src/main/java/cc/yunxi/common/advice/CommonExceptionAdvice.java b/nxhs-common/src/main/java/cc/yunxi/common/advice/CommonExceptionAdvice.java index d2a4eab..2a85235 100644 --- a/nxhs-common/src/main/java/cc/yunxi/common/advice/CommonExceptionAdvice.java +++ b/nxhs-common/src/main/java/cc/yunxi/common/advice/CommonExceptionAdvice.java @@ -6,26 +6,36 @@ import cc.yunxi.common.exception.DbException; import cc.yunxi.common.domain.CommonResult; import cc.yunxi.common.utils.WebUtils; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.validation.FieldError; import org.springframework.validation.ObjectError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.util.NestedServletException; +import javax.servlet.http.HttpServletRequest; +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import javax.validation.Path; import java.net.BindException; -import java.util.stream.Collectors; +import java.util.Iterator; +import java.util.List; @RestControllerAdvice @Slf4j public class CommonExceptionAdvice { + @ResponseBody @ExceptionHandler(DbException.class) public Object handleDbException(DbException e) { log.error("mysql数据库操作异常 -> ", e); return processResponse(e); } + @ResponseBody @ExceptionHandler(CommonException.class) public Object handleBadRequestException(CommonException e) { log.error("自定义异常 -> {} , 异常原因:{} ",e.getClass().getName(), e.getMessage()); @@ -33,15 +43,46 @@ public class CommonExceptionAdvice { return processResponse(e); } - @ExceptionHandler(MethodArgumentNotValidException.class) - public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { - String msg = e.getBindingResult().getAllErrors() - .stream().map(ObjectError::getDefaultMessage) - .collect(Collectors.joining("|")); - log.error("请求参数校验异常 -> {}", msg); + + /** + * 参数校验异常类型 + */ + @ResponseBody + @ExceptionHandler({org.springframework.validation.BindException.class, ConstraintViolationException.class, MethodArgumentNotValidException.class}) + public Object handleException(Exception e, HttpServletRequest request) { + StringBuffer errorMsg = new StringBuffer(); + if (!(e instanceof org.springframework.validation.BindException) && !(e instanceof MethodArgumentNotValidException)) { + for (ConstraintViolation cv : ((ConstraintViolationException) e).getConstraintViolations()) { + Iterator it = cv.getPropertyPath().iterator(); + Path.Node last = null; + while (it.hasNext()) { + last = it.next(); + } + errorMsg.append(last != null ? last.getName() : "").append(":").append(cv.getMessageTemplate()).append(";"); +// errorMsg.append(cv.getMessageTemplate()).append(";"); + } + } else { + List allErrors; + if (e instanceof org.springframework.validation.BindException) { + allErrors = ((org.springframework.validation.BindException) e).getAllErrors(); + } else { + allErrors = ((MethodArgumentNotValidException) e).getBindingResult().getAllErrors(); + } + // 拼接错误信息 + for (ObjectError oe : allErrors) { + if (oe instanceof FieldError) { + errorMsg.append(((FieldError) oe).getField()).append(":").append(oe.getDefaultMessage()).append(";"); +// errorMsg.append(oe.getDefaultMessage()).append(";"); + } else { + errorMsg.append(oe.getObjectName()).append(":").append(oe.getDefaultMessage()).append(";"); + } + } + } + log.error("请求参数校验异常 -> {}", errorMsg); log.debug("", e); - return processResponse(new BadRequestException(msg)); + return processResponse(new BadRequestException(errorMsg.toString())); } + @ExceptionHandler(BindException.class) public Object handleBindException(BindException e) { log.error("请求参数绑定异常 ->BindException, {}", e.getMessage()); @@ -63,6 +104,9 @@ public class CommonExceptionAdvice { } private ResponseEntity> processResponse(CommonException e){ - return ResponseEntity.status(e.getCode()).body(CommonResult.error(e)); + return ResponseEntity.status(e.getCode()) + .contentType(MediaType.APPLICATION_JSON) + .header("Content-Encoding", "UTF-8") + .body(CommonResult.error(e)); } } diff --git a/nxhs-common/src/main/java/cc/yunxi/common/config/JsonConfig.java b/nxhs-common/src/main/java/cc/yunxi/common/config/JsonConfig.java index 15ec22a..4655261 100644 --- a/nxhs-common/src/main/java/cc/yunxi/common/config/JsonConfig.java +++ b/nxhs-common/src/main/java/cc/yunxi/common/config/JsonConfig.java @@ -1,7 +1,9 @@ package cc.yunxi.common.config; -import cc.yunxi.common.domain.LocalDateTimeDeserializer; -import cc.yunxi.common.domain.LocalDateTimeSerializer; +import cc.yunxi.common.domain.convert.EnumDeserializer; +import cc.yunxi.common.domain.convert.EnumSerializer; +import cc.yunxi.common.domain.convert.LocalDateTimeDeserializer; +import cc.yunxi.common.domain.convert.LocalDateTimeSerializer; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import lombok.extern.slf4j.Slf4j; @@ -29,9 +31,13 @@ public class JsonConfig { jacksonObjectMapperBuilder .serializerByType(Long.class, ToStringSerializer.instance) .serializerByType(BigInteger.class, ToStringSerializer.instance); - // LocalDateTime -> Instant + // LocalDateTime <-> Instant / String jacksonObjectMapperBuilder.serializerByType(LocalDateTime.class, LocalDateTimeSerializer.INSTANCE); jacksonObjectMapperBuilder.deserializerByType(LocalDateTime.class, LocalDateTimeDeserializer.INSTANCE); + // String <-> Enum +// jacksonObjectMapperBuilder.serializerByType(Enum.class, EnumSerializer.INSTANCE); +// jacksonObjectMapperBuilder.deserializerByType(Enum.class, EnumDeserializer.INSTANCE); + }; } } \ No newline at end of file diff --git a/nxhs-common/src/main/java/cc/yunxi/common/config/SwaggerEnumPlugin.java b/nxhs-common/src/main/java/cc/yunxi/common/config/SwaggerEnumPlugin.java new file mode 100644 index 0000000..eb97185 --- /dev/null +++ b/nxhs-common/src/main/java/cc/yunxi/common/config/SwaggerEnumPlugin.java @@ -0,0 +1,186 @@ +package cc.yunxi.common.config; + +import cc.yunxi.common.enums.SwaggerDisplayEnum; +import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import com.fasterxml.classmate.ResolvedType; +import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.ReflectionUtils; +import springfox.documentation.builders.ModelPropertyBuilder; +import springfox.documentation.builders.OperationBuilder; +import springfox.documentation.builders.ParameterBuilder; +import springfox.documentation.service.AllowableListValues; +import springfox.documentation.service.ResolvedMethodParameter; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spi.schema.ModelPropertyBuilderPlugin; +import springfox.documentation.spi.schema.contexts.ModelPropertyContext; +import springfox.documentation.spi.service.ExpandedParameterBuilderPlugin; +import springfox.documentation.spi.service.OperationBuilderPlugin; +import springfox.documentation.spi.service.ParameterBuilderPlugin; +import springfox.documentation.spi.service.contexts.OperationContext; +import springfox.documentation.spi.service.contexts.ParameterContext; +import springfox.documentation.spi.service.contexts.ParameterExpansionContext; + +import java.lang.reflect.Field; +import java.lang.reflect.Parameter; +import java.util.*; +import java.util.stream.Collectors; + +/** + * Swagger枚举转换扩展配置类 + */ +@Configuration +@Slf4j +public class SwaggerEnumPlugin implements ModelPropertyBuilderPlugin, ParameterBuilderPlugin, OperationBuilderPlugin, ExpandedParameterBuilderPlugin { + + private static final String DELIMITER = ","; + + @Override + public void apply(ModelPropertyContext context) { + Optional optional = context.getBeanPropertyDefinition(); + if (!optional.isPresent()) { + return; + } + + Class fieldType = optional.get().getField().getRawType(); + addDescForEnum(context, fieldType); + } + + private void addDescForEnum(ModelPropertyContext context, Class fieldType) { + if (Enum.class.isAssignableFrom(fieldType)) { + SwaggerDisplayEnum anno = AnnotationUtils.findAnnotation(fieldType, SwaggerDisplayEnum.class); + if (anno != null) { + Object[] enumConstants = fieldType.getEnumConstants(); + List displayValues = getDisplayValues(anno, enumConstants); + + ModelPropertyBuilder builder = context.getBuilder(); + Field descField = ReflectionUtils.findField(builder.getClass(), "description"); + ReflectionUtils.makeAccessible(descField); + String joinText = (ReflectionUtils.getField(descField, builder) == null ? "" : (ReflectionUtils.getField(descField, builder) + ":")) + + String.join(DELIMITER, displayValues); + +// builder.description(joinText).type(context.getResolver().resolve(Integer.class)); + builder.description(joinText).type(context.getResolver().resolve(String.class)); + } + } + } + + @Override + public void apply(OperationContext context) { + Map> map = new HashMap<>(); + List parameters = context.getParameters(); + parameters.forEach(parameter -> { + ResolvedType parameterType = parameter.getParameterType(); + Class clazz = parameterType.getErasedType(); + if (Enum.class.isAssignableFrom(clazz)) { + SwaggerDisplayEnum annotation = AnnotationUtils.findAnnotation(clazz, SwaggerDisplayEnum.class); + if (annotation != null) { + Object[] enumConstants = clazz.getEnumConstants(); + List displayValues = getDisplayValues(annotation, enumConstants); + map.put(parameter.defaultName().orElse(""), displayValues); + + OperationBuilder operationBuilder = context.operationBuilder(); + Field parametersField = ReflectionUtils.findField(operationBuilder.getClass(), "parameters"); + ReflectionUtils.makeAccessible(parametersField); + List list = (List) ReflectionUtils.getField(parametersField, operationBuilder); + + map.forEach((k, v) -> { + for (Parameter currentParameter : list) { + if (StringUtils.equals(currentParameter.getName(), k)) { + Field description = ReflectionUtils.findField(currentParameter.getClass(), "description"); + ReflectionUtils.makeAccessible(description); + Object field = ReflectionUtils.getField(description, currentParameter); + ReflectionUtils.setField(description, currentParameter, field + ":" + String.join(DELIMITER, v)); + break; + } + } + }); + } + } + }); + } + + @Override + public void apply(ParameterContext context) { + Class type = context.resolvedMethodParameter().getParameterType().getErasedType(); + ParameterBuilder parameterBuilder = context.parameterBuilder(); + setAvailableValue(parameterBuilder, type); + } + + private void setAvailableValue(ParameterBuilder parameterBuilder, Class type) { + if (Enum.class.isAssignableFrom(type)) { + SwaggerDisplayEnum annotation = AnnotationUtils.findAnnotation(type, SwaggerDisplayEnum.class); + if (annotation != null) { + String code = annotation.code(); + Object[] enumConstants = type.getEnumConstants(); + List displayValues = Arrays.stream(enumConstants).filter(Objects::nonNull).map(item -> { + Class currentClass = item.getClass(); + + Field codeField = ReflectionUtils.findField(currentClass, code); + assert codeField != null; + ReflectionUtils.makeAccessible(codeField); + Object codeStr = ReflectionUtils.getField(codeField, item); + assert codeStr != null; + return codeStr.toString(); + + }).collect(Collectors.toList()); + + // 设置可用值 + AllowableListValues values = new AllowableListValues(displayValues, "LIST"); + parameterBuilder.allowableValues(values); + } + } + } + + @Override + public void apply(ParameterExpansionContext context) { + Class type = context.getFieldType().getErasedType(); + ParameterBuilder parameterBuilder = context.getParameterBuilder(); + if (Enum.class.isAssignableFrom(type)) { + setAvailableValue(parameterBuilder, type); + SwaggerDisplayEnum annotation = AnnotationUtils.findAnnotation(type, SwaggerDisplayEnum.class); + if (annotation != null) { + Object[] enumConstants = type.getEnumConstants(); + List displayValues = getDisplayValues(annotation, enumConstants); + + Field descField = ReflectionUtils.findField(parameterBuilder.getClass(), "description"); + ReflectionUtils.makeAccessible(descField); + String joinText = (ReflectionUtils.getField(descField, parameterBuilder) == null ? "" : (ReflectionUtils.getField(descField, parameterBuilder) + ":")) + + String.join(DELIMITER, displayValues); + + parameterBuilder.description(joinText); + } + } + } + + private List getDisplayValues(SwaggerDisplayEnum annotation, Object[] enumConstants) { + if (annotation == null) { + return new ArrayList<>(); + } + String code = annotation.code(); + String desc = annotation.desc(); + return Arrays.stream(enumConstants).filter(Objects::nonNull).map( + item -> { + Class currentClass = item.getClass(); + Field codeField = ReflectionUtils.findField(currentClass, code); + assert codeField != null; + ReflectionUtils.makeAccessible(codeField); + Object codeStr = ReflectionUtils.getField(codeField, item); + + Field descField = ReflectionUtils.findField(currentClass, desc); + assert descField != null; + ReflectionUtils.makeAccessible(descField); + Object descStr = ReflectionUtils.getField(descField, item); + + return codeStr + "、" + descStr; + } + ).collect(Collectors.toList()); + } + + @Override + public boolean supports(DocumentationType documentationType) { + return true; + } +} diff --git a/nxhs-common/src/main/java/cc/yunxi/common/domain/PageDTO.java b/nxhs-common/src/main/java/cc/yunxi/common/domain/PageDTO.java index 4286e2d..7e270ac 100644 --- a/nxhs-common/src/main/java/cc/yunxi/common/domain/PageDTO.java +++ b/nxhs-common/src/main/java/cc/yunxi/common/domain/PageDTO.java @@ -1,6 +1,7 @@ package cc.yunxi.common.domain; +import cc.yunxi.common.domain.function.Convert; import cc.yunxi.common.utils.BeanUtils; import cc.yunxi.common.utils.CollUtils; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; diff --git a/nxhs-common/src/main/java/cc/yunxi/common/domain/PageQuery.java b/nxhs-common/src/main/java/cc/yunxi/common/domain/PageQuery.java index d0160e0..0a5ec4e 100644 --- a/nxhs-common/src/main/java/cc/yunxi/common/domain/PageQuery.java +++ b/nxhs-common/src/main/java/cc/yunxi/common/domain/PageQuery.java @@ -1,5 +1,6 @@ package cc.yunxi.common.domain; +import cc.yunxi.common.domain.function.SFunction; import cc.yunxi.common.utils.LambdaUtil; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.StrUtil; diff --git a/nxhs-common/src/main/java/cc/yunxi/common/domain/convert/EnumDeserializer.java b/nxhs-common/src/main/java/cc/yunxi/common/domain/convert/EnumDeserializer.java new file mode 100644 index 0000000..7742b7e --- /dev/null +++ b/nxhs-common/src/main/java/cc/yunxi/common/domain/convert/EnumDeserializer.java @@ -0,0 +1,65 @@ +package cc.yunxi.common.domain.convert; + +import cc.yunxi.common.enums.BaseEnum; +import cc.yunxi.common.factory.StringCodeToEnumConverterFactory; +import com.baomidou.mybatisplus.annotation.IEnum; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.deser.ContextualDeserializer; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.springframework.util.StringUtils; + +import java.io.IOException; + +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class EnumDeserializer extends JsonDeserializer implements ContextualDeserializer { + + private Class target; + + public static final EnumDeserializer INSTANCE = new EnumDeserializer(); + + @SuppressWarnings("all") + @Override + public BaseEnum deserialize(JsonParser jsonParser, DeserializationContext ctx) throws IOException { + if (!StringUtils.hasText(jsonParser.getText())) { + return null; + } + // BaseEnum类 = target类相同 | tartget类的父类或接口 + if (BaseEnum.class.isAssignableFrom(target)) { + return StringCodeToEnumConverterFactory.getEnum((Class) target, jsonParser.getText()); + } + return defaultEnumTransform(target,jsonParser.getText()); + } + + /** + * @param ctx ctx + * @param property property + * @return 1 + * @throws JsonMappingException + */ + @Override + public JsonDeserializer createContextual(DeserializationContext ctx, BeanProperty property) throws JsonMappingException { + Class rawCls = ctx.getContextualType().getRawClass(); + EnumDeserializer enumDeserializer = new EnumDeserializer(); + enumDeserializer.setTarget(rawCls); + return enumDeserializer; + } + + + public static BaseEnum defaultEnumTransform(Class type, String indexString) { + BaseEnum[] enumConstants = (BaseEnum[]) type.getEnumConstants(); + try { + int index = Integer.parseInt(indexString); + return enumConstants[index]; + } catch (NumberFormatException e) { + return null; + } + } +} diff --git a/nxhs-common/src/main/java/cc/yunxi/common/domain/convert/EnumSerializer.java b/nxhs-common/src/main/java/cc/yunxi/common/domain/convert/EnumSerializer.java new file mode 100644 index 0000000..78540a7 --- /dev/null +++ b/nxhs-common/src/main/java/cc/yunxi/common/domain/convert/EnumSerializer.java @@ -0,0 +1,39 @@ +package cc.yunxi.common.domain.convert; + +import cc.yunxi.common.enums.BaseEnum; +import cc.yunxi.common.factory.StringCodeToEnumConverterFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.deser.ContextualDeserializer; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.springframework.util.StringUtils; + +import java.io.IOException; +import java.lang.reflect.Field; + +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class EnumSerializer extends JsonSerializer { + + public static final EnumSerializer INSTANCE = new EnumSerializer(); + + + @Override + public void serialize(BaseEnum baseEnum, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + // java 如何获取枚举类的成员属性? 此处有问题, todo + jsonGenerator.writeStartObject(); + Field[] fields = baseEnum.getClass().getFields(); + for (Field field : fields) { + try { + jsonGenerator.writeStringField(field.getName(), String.valueOf(field.get(baseEnum))); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + jsonGenerator.writeEndObject(); + } +} diff --git a/nxhs-common/src/main/java/cc/yunxi/common/domain/LocalDateTimeDeserializer.java b/nxhs-common/src/main/java/cc/yunxi/common/domain/convert/LocalDateTimeDeserializer.java similarity index 88% rename from nxhs-common/src/main/java/cc/yunxi/common/domain/LocalDateTimeDeserializer.java rename to nxhs-common/src/main/java/cc/yunxi/common/domain/convert/LocalDateTimeDeserializer.java index 842cebc..07cfacd 100644 --- a/nxhs-common/src/main/java/cc/yunxi/common/domain/LocalDateTimeDeserializer.java +++ b/nxhs-common/src/main/java/cc/yunxi/common/domain/convert/LocalDateTimeDeserializer.java @@ -1,10 +1,8 @@ -package cc.yunxi.common.domain; +package cc.yunxi.common.domain.convert; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; -import org.springframework.format.annotation.DateTimeFormat; import java.io.IOException; import java.time.Instant; diff --git a/nxhs-common/src/main/java/cc/yunxi/common/domain/LocalDateTimeSerializer.java b/nxhs-common/src/main/java/cc/yunxi/common/domain/convert/LocalDateTimeSerializer.java similarity index 96% rename from nxhs-common/src/main/java/cc/yunxi/common/domain/LocalDateTimeSerializer.java rename to nxhs-common/src/main/java/cc/yunxi/common/domain/convert/LocalDateTimeSerializer.java index d0d5f9a..211ce24 100644 --- a/nxhs-common/src/main/java/cc/yunxi/common/domain/LocalDateTimeSerializer.java +++ b/nxhs-common/src/main/java/cc/yunxi/common/domain/convert/LocalDateTimeSerializer.java @@ -1,4 +1,4 @@ -package cc.yunxi.common.domain; +package cc.yunxi.common.domain.convert; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; diff --git a/nxhs-common/src/main/java/cc/yunxi/common/domain/Convert.java b/nxhs-common/src/main/java/cc/yunxi/common/domain/function/Convert.java similarity index 68% rename from nxhs-common/src/main/java/cc/yunxi/common/domain/Convert.java rename to nxhs-common/src/main/java/cc/yunxi/common/domain/function/Convert.java index 04b8b14..e261ee5 100644 --- a/nxhs-common/src/main/java/cc/yunxi/common/domain/Convert.java +++ b/nxhs-common/src/main/java/cc/yunxi/common/domain/function/Convert.java @@ -1,8 +1,9 @@ -package cc.yunxi.common.domain; +package cc.yunxi.common.domain.function; /** * 对原对象进行计算,设置到目标对象中 **/ +@FunctionalInterface public interface Convert{ void convert(R origin, T target); } \ No newline at end of file diff --git a/nxhs-common/src/main/java/cc/yunxi/common/domain/SFunction.java b/nxhs-common/src/main/java/cc/yunxi/common/domain/function/SFunction.java similarity index 80% rename from nxhs-common/src/main/java/cc/yunxi/common/domain/SFunction.java rename to nxhs-common/src/main/java/cc/yunxi/common/domain/function/SFunction.java index 53f83a7..62acfe3 100644 --- a/nxhs-common/src/main/java/cc/yunxi/common/domain/SFunction.java +++ b/nxhs-common/src/main/java/cc/yunxi/common/domain/function/SFunction.java @@ -1,4 +1,4 @@ -package cc.yunxi.common.domain; +package cc.yunxi.common.domain.function; import java.io.Serializable; diff --git a/nxhs-common/src/main/java/cc/yunxi/common/enums/BaseEnum.java b/nxhs-common/src/main/java/cc/yunxi/common/enums/BaseEnum.java new file mode 100644 index 0000000..b14bf21 --- /dev/null +++ b/nxhs-common/src/main/java/cc/yunxi/common/enums/BaseEnum.java @@ -0,0 +1,11 @@ +package cc.yunxi.common.enums; + +/** + * 枚举接口 + */ +public interface BaseEnum { + /** + * 获取枚举编码 + */ + String getCode(); +} diff --git a/nxhs-common/src/main/java/cc/yunxi/common/enums/SwaggerDisplayEnum.java b/nxhs-common/src/main/java/cc/yunxi/common/enums/SwaggerDisplayEnum.java new file mode 100644 index 0000000..1a7a5f3 --- /dev/null +++ b/nxhs-common/src/main/java/cc/yunxi/common/enums/SwaggerDisplayEnum.java @@ -0,0 +1,16 @@ +package cc.yunxi.common.enums; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Swagger枚举转换注解 + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface SwaggerDisplayEnum { + String code() default "code"; + String desc() default "desc"; +} \ No newline at end of file diff --git a/nxhs-common/src/main/java/cc/yunxi/common/factory/StringCodeToEnumConverterFactory.java b/nxhs-common/src/main/java/cc/yunxi/common/factory/StringCodeToEnumConverterFactory.java new file mode 100644 index 0000000..fe69044 --- /dev/null +++ b/nxhs-common/src/main/java/cc/yunxi/common/factory/StringCodeToEnumConverterFactory.java @@ -0,0 +1,71 @@ +package cc.yunxi.common.factory; + +import cc.yunxi.common.enums.BaseEnum; +import com.baomidou.mybatisplus.annotation.IEnum; +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.converter.ConverterFactory; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * 编码 -> 枚举 转化器工厂类 + */ +public class StringCodeToEnumConverterFactory implements ConverterFactory { + private static final Map CONVERTERS = new HashMap<>(); + + /** + * 获取一个从 String 转化为 T 的转换器,T 是一个泛型,有多个实现 + * + * @param targetType 转换后的类型 + * @return 返回一个转化器 + */ + @Override + public Converter getConverter(Class targetType) { + Converter converter = CONVERTERS.get(targetType); + if (converter == null) { + converter = new StringToEnumConverter<>(targetType); + CONVERTERS.put(targetType, converter); + } + return converter; + } + + /** + * 获取枚举实例 + * @param targetType 枚举类 + * @param source 枚举编码 + * @return T + */ + public static T getEnum(Class targetType, String source) { + StringToEnumConverter enumConverter = new StringToEnumConverter(targetType); + return (T) enumConverter.convert(source); + } + + /** + * 枚举编码 -> 枚举 转化器 + * 实现org.springframework.core.convert.converter.Converter类 + */ + public static class StringToEnumConverter implements Converter { + + private final Map enumMap = new HashMap<>(); + + + public StringToEnumConverter(Class enumType) { + T[] enums = enumType.getEnumConstants(); + for (T e : enums) { + enumMap.put(e.getCode().toString(), e); + } + } + + @Override + public T convert(String source) { + T t = enumMap.get(source); +// Enum.valueOf(enumType, source); todo + if (Objects.isNull(t)) { + throw new IllegalArgumentException("无法匹配对应的枚举类型"); + } + return t; + } + } +} diff --git a/nxhs-common/src/main/java/cc/yunxi/common/utils/BeanUtils.java b/nxhs-common/src/main/java/cc/yunxi/common/utils/BeanUtils.java index f42864c..8abcf3e 100644 --- a/nxhs-common/src/main/java/cc/yunxi/common/utils/BeanUtils.java +++ b/nxhs-common/src/main/java/cc/yunxi/common/utils/BeanUtils.java @@ -1,6 +1,6 @@ package cc.yunxi.common.utils; -import cc.yunxi.common.domain.Convert; +import cc.yunxi.common.domain.function.Convert; import cn.hutool.core.bean.BeanUtil; import java.util.List; diff --git a/nxhs-common/src/main/java/cc/yunxi/common/utils/LambdaUtil.java b/nxhs-common/src/main/java/cc/yunxi/common/utils/LambdaUtil.java index a02f7ae..54d0dc7 100644 --- a/nxhs-common/src/main/java/cc/yunxi/common/utils/LambdaUtil.java +++ b/nxhs-common/src/main/java/cc/yunxi/common/utils/LambdaUtil.java @@ -1,6 +1,6 @@ package cc.yunxi.common.utils; -import cc.yunxi.common.domain.SFunction; +import cc.yunxi.common.domain.function.SFunction; import lombok.extern.slf4j.Slf4j; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; diff --git a/nxhs-service/src/main/java/cc/yunxi/NxhsApplication.java b/nxhs-service/src/main/java/cc/yunxi/NxhsApplication.java index f802f5b..f07c6d9 100644 --- a/nxhs-service/src/main/java/cc/yunxi/NxhsApplication.java +++ b/nxhs-service/src/main/java/cc/yunxi/NxhsApplication.java @@ -4,7 +4,7 @@ import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -@MapperScan("cc.yunxi.mapper") +@MapperScan(basePackages = "cc.yunxi.mapper") @SpringBootApplication public class NxhsApplication { public static void main(String[] args) { diff --git a/nxhs-service/src/main/java/cc/yunxi/aspect/JudgeUserAspect.java b/nxhs-service/src/main/java/cc/yunxi/aspect/JudgeUserAspect.java index 4e321fa..0cd2115 100644 --- a/nxhs-service/src/main/java/cc/yunxi/aspect/JudgeUserAspect.java +++ b/nxhs-service/src/main/java/cc/yunxi/aspect/JudgeUserAspect.java @@ -36,7 +36,7 @@ public class JudgeUserAspect { UserTypeEnum typeEnum = annotation.value(); UserDTO currentUser = UserContext.getUser(); log.info("请求控制器: {}, 当前用户信息: {}", methodSignature, currentUser); - if (currentUser == null || !Objects.equals(currentUser.getUserType(), typeEnum.getType())) { + if (currentUser == null || !Objects.equals(currentUser.getUserType(), typeEnum.getCode())) { throw new UnauthorizedException("非法请求!"); } } catch (Exception exception) { diff --git a/nxhs-service/src/main/java/cc/yunxi/config/WebMvcConfig.java b/nxhs-service/src/main/java/cc/yunxi/config/WebMvcConfig.java index 156688d..3c68fd2 100644 --- a/nxhs-service/src/main/java/cc/yunxi/config/WebMvcConfig.java +++ b/nxhs-service/src/main/java/cc/yunxi/config/WebMvcConfig.java @@ -1,15 +1,17 @@ package cc.yunxi.config; +import cc.yunxi.common.factory.StringCodeToEnumConverterFactory; import cc.yunxi.interceptor.LoginInterceptor; import cc.yunxi.interceptor.StatsInterceptor; import cn.hutool.core.collection.CollUtil; +import lombok.Getter; import lombok.RequiredArgsConstructor; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.InterceptorRegistration; -import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.format.FormatterRegistry; +import org.springframework.util.AntPathMatcher; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.config.annotation.*; import java.util.Arrays; import java.util.List; @@ -30,9 +32,48 @@ public class WebMvcConfig implements WebMvcConfigurer { "/v3/**", "/swagger-resources/**", "/webjars/**", + "/static/**", "/doc.html" ); + /** + * controller包下统一前缀/api + */ + private Api api = new Api("/api", "cc.yunxi.**.controller.**"); + + @Getter + public static class Api { + /** + * 请求前缀 + */ + private String prefix; + + /** + * 匹配包名 + */ + private String controllerPath; + + public Api(String prefix, String controllerPath) { + this.prefix = prefix; + this.controllerPath = controllerPath; + } + } + + /** + * 配置静态资源访问路径 + */ + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + // 静态资源访问路径和存放路径配置 + registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/","classpath:/public/"); + // swagger访问配置 + registry.addResourceHandler("/**").addResourceLocations("classpath:/META-INF/resources/","classpath:/META-INF/resources/webjars/"); + } + + + /** + * 跨域问题 + */ @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") // 所有接口 @@ -44,6 +85,40 @@ public class WebMvcConfig implements WebMvcConfigurer { } + /** + * 配置接口统一前缀 + */ + @Override + public void configurePathMatch(PathMatchConfigurer configurer) { + // 根据不同包匹配表达式,添加各自的统一前缀 + configurePathMatch(configurer, api); + } + + /** + * API 接口前缀:实现指定的controller 提供的 RESTFul API 的统一前缀(通过该前缀,避免Swagger,Actuator 意外通过Nginx暴露出来给外部,带来安全性问题) + * + * @param configurer + * @param api + */ + private void configurePathMatch(PathMatchConfigurer configurer, Api api) { + // 创建路径匹配类,指定以'.'分隔 + AntPathMatcher antPathMatcher = new AntPathMatcher("."); + // 指定匹配前缀 类上有RestController注解 && 该类的包名匹配指定的自定义包的表达式 + configurer.addPathPrefix(api.getPrefix(), clazz -> clazz.isAnnotationPresent(RestController.class) + && antPathMatcher.match(api.getControllerPath(), clazz.getPackage().getName())); + } + + /** + * 枚举类的转换器工厂 addConverterFactory + * 注意: get参数请求时触发 + */ + @Override + public void addFormatters(FormatterRegistry registry) { + // 枚举 转化器工厂类 code编码 => 枚举 + registry.addConverterFactory(new StringCodeToEnumConverterFactory()); + } + + @Override public void addInterceptors(InterceptorRegistry registry) { // 按顺序执行拦截器 diff --git a/nxhs-service/src/main/java/cc/yunxi/config/WxShProperties.java b/nxhs-service/src/main/java/cc/yunxi/config/WxShProperties.java new file mode 100644 index 0000000..97cc839 --- /dev/null +++ b/nxhs-service/src/main/java/cc/yunxi/config/WxShProperties.java @@ -0,0 +1,17 @@ +package cc.yunxi.config; + + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 回收员微信配置 + */ +@Data +@Component +@ConfigurationProperties(prefix = "nxhs.wx.client") +public class WxShProperties { + private String appId; + private String appSecret; +} diff --git a/nxhs-service/src/main/java/cc/yunxi/controller/ClientController.java b/nxhs-service/src/main/java/cc/yunxi/controller/ClientController.java index 5a1cb63..00a9300 100644 --- a/nxhs-service/src/main/java/cc/yunxi/controller/ClientController.java +++ b/nxhs-service/src/main/java/cc/yunxi/controller/ClientController.java @@ -8,6 +8,7 @@ import cc.yunxi.domain.query.ClientQuery; import cc.yunxi.domain.vo.ClientVO; import cc.yunxi.service.IClientService; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.PostMapping; @@ -24,6 +25,7 @@ import org.springframework.web.bind.annotation.RestController; * @author ccongli * @since 2024-02-28 06:08:09 */ +@Api(tags = "散户接口") @RestController @RequestMapping("/client") @RequiredArgsConstructor diff --git a/nxhs-service/src/main/java/cc/yunxi/controller/CommonController.java b/nxhs-service/src/main/java/cc/yunxi/controller/CommonController.java index d67f5ff..5488e1d 100644 --- a/nxhs-service/src/main/java/cc/yunxi/controller/CommonController.java +++ b/nxhs-service/src/main/java/cc/yunxi/controller/CommonController.java @@ -33,47 +33,25 @@ import javax.validation.Valid; @Slf4j public class CommonController { - private final String code2SessionUrl = "https://api.weixin.qq.com/sns/jscode2session"; - private final CommonService commonService; - @Resource - private WxHsyProperties wxHsyProperties; - @Resource private JwtTool jwtTool; - @ApiOperation("登录") - @PostMapping("/login") - public CommonResult login(@Valid @RequestBody WxLoginDTO wxLoginDTO) { - log.info("login request body:{}", wxLoginDTO); - String url = code2SessionUrl +"?appid=" + wxHsyProperties.getAppId() + - "&secret="+ wxHsyProperties.getAppSecret() + - "&js_code=" + wxLoginDTO.getCode() + - "&grant_type=authorization_code"; - String jsonStr = HttpUtil.get(url, 30000); - log.info("WXserver code2Session return {}", jsonStr); - JSONObject jsonObject = JSONUtil.parseObj(jsonStr); - if (StrUtil.contains(jsonStr, "errcode")) { - throw new BizIllegalException("微信登录认证失败"); - } - String openId = jsonObject.getStr("openid"); - String sessionKey = jsonObject.getStr("session_key"); -// String unionId = jsonObject.getStr("unionid"); - // 解密得到用户手机号 - String userInfo = WeChatUtil.decryptData(wxLoginDTO.getEncryptedData(), sessionKey, wxLoginDTO.getIv()); - if (StringUtils.isEmpty(userInfo)) { - throw new BizIllegalException("微信认证信息错误"); - } - // 判断用户类型,匹配登录时业务 - UserDTO userDTO; - if(wxLoginDTO.getUserType().equals(UserTypeEnum.CLIENT.getType())) { - // 散户端登录业务 - userDTO = commonService.loginByClient(userInfo, openId); - } else { - // 回收员端登录业务 - userDTO = commonService.loginByRecycler(userInfo, openId); - } + @ApiOperation("回收员登录") + @PostMapping("/hsylogin") + public CommonResult hsyLogin(@Valid @RequestBody WxLoginDTO wxLoginDTO) { + UserDTO userDTO = commonService.loginByRecycler(wxLoginDTO); + String token = jwtTool.createToken(userDTO); + userDTO.setToken(token); + return CommonResult.success(userDTO); + } + + @ApiOperation("散户登录") + @PostMapping("/shlogin") + public CommonResult shLogin(@Valid @RequestBody WxLoginDTO wxLoginDTO) { + // 散户端登录业务 + UserDTO userDTO = commonService.loginByClient(wxLoginDTO); String token = jwtTool.createToken(userDTO); userDTO.setToken(token); return CommonResult.success(userDTO); diff --git a/nxhs-service/src/main/java/cc/yunxi/controller/RecycleOrderController.java b/nxhs-service/src/main/java/cc/yunxi/controller/RecycleOrderController.java index 0ec0f2c..d62fd8c 100644 --- a/nxhs-service/src/main/java/cc/yunxi/controller/RecycleOrderController.java +++ b/nxhs-service/src/main/java/cc/yunxi/controller/RecycleOrderController.java @@ -4,10 +4,13 @@ package cc.yunxi.controller; import cc.yunxi.aspect.UserTypeAnnotation; import cc.yunxi.common.domain.CommonResult; import cc.yunxi.common.domain.PageDTO; +import cc.yunxi.common.utils.BeanUtils; import cc.yunxi.domain.vo.recycleorder.RecycleOrderCreateVO; import cc.yunxi.domain.po.RecycleOrder; import cc.yunxi.domain.query.RecycleOrderQuery; import cc.yunxi.domain.vo.recycleorder.RecycleOrderRespVO; +import cc.yunxi.domain.vo.recycleorder.RecycleOrderTakingVO; +import cc.yunxi.domain.vo.recycleorder.RecycleOrderUpdateVO; import cc.yunxi.enums.UserTypeEnum; import cc.yunxi.service.IRecycleOrderService; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; @@ -15,17 +18,13 @@ import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; - -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import javax.validation.Valid; /** *

- * 回收站回收订单 前端控制器 + * 回收站回收订单 *

* * @author ccongli @@ -52,10 +51,35 @@ public class RecycleOrderController { @ApiOperation("创建回收订单") @PostMapping("/create") @UserTypeAnnotation(UserTypeEnum.CLIENT) - public CommonResult createOrder(@Valid @RequestBody RecycleOrderCreateVO orderCreateVO) { - // 1.分页查询 + public CommonResult createOrder(@RequestBody RecycleOrderCreateVO orderCreateVO) { String orderId = recycleOrderService.createOrder(orderCreateVO); return CommonResult.success(orderId); } + + @ApiOperation("回收订单详情") + @GetMapping("/info") + public CommonResult findOrder(@RequestParam("id") String id) { + RecycleOrder recycleOrder = recycleOrderService.getOrderById(id); + RecycleOrderRespVO recycleOrderRespVO = BeanUtils.copyBean(recycleOrder, RecycleOrderRespVO.class); + return CommonResult.success(recycleOrderRespVO); + } + + @ApiOperation("回收订单更新") + @PostMapping("/update") + @UserTypeAnnotation(UserTypeEnum.CLIENT) + public CommonResult updateOrder(@RequestBody RecycleOrderUpdateVO orderUpdateVO) { + recycleOrderService.updateOrder(orderUpdateVO); + return CommonResult.success(true); + } + + @ApiOperation("回收员接单") + @PostMapping("/taking") + @UserTypeAnnotation(UserTypeEnum.CLIENT) + public CommonResult takingOrder(@RequestBody RecycleOrderTakingVO orderTakingVO) { + recycleOrderService.takingOrder(orderTakingVO); + return CommonResult.success(true); + } + + } diff --git a/nxhs-service/src/main/java/cc/yunxi/controller/TestController.java b/nxhs-service/src/main/java/cc/yunxi/controller/TestController.java index 06a528f..ca46b5b 100644 --- a/nxhs-service/src/main/java/cc/yunxi/controller/TestController.java +++ b/nxhs-service/src/main/java/cc/yunxi/controller/TestController.java @@ -2,13 +2,13 @@ package cc.yunxi.controller; import cc.yunxi.common.domain.CommonResult; import cc.yunxi.common.exception.BadRequestException; +import cc.yunxi.domain.query.TestQuery; +import cc.yunxi.enums.UserTypeEnum; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @Api(tags = "测试接口") @RestController @@ -39,4 +39,12 @@ public class TestController { log.error("error log..."); return CommonResult.success("ok"); } + + @ApiOperation("测试枚举转换") + @PostMapping("/test04") + public CommonResult enumConvert(@RequestBody TestQuery testQuery) { + + return CommonResult.success(testQuery); + } + } diff --git a/nxhs-service/src/main/java/cc/yunxi/domain/dto/UserDTO.java b/nxhs-service/src/main/java/cc/yunxi/domain/dto/UserDTO.java index edd0b40..73aa6c1 100644 --- a/nxhs-service/src/main/java/cc/yunxi/domain/dto/UserDTO.java +++ b/nxhs-service/src/main/java/cc/yunxi/domain/dto/UserDTO.java @@ -8,7 +8,7 @@ import lombok.Data; @Data public class UserDTO { - private Integer userType; // 1 回收员 2 散户 + private String userType; // 1 回收员 2 散户 private String id; @@ -16,6 +16,8 @@ public class UserDTO { private String phone; + private String openid; + private String token; // 访问token } diff --git a/nxhs-service/src/main/java/cc/yunxi/domain/dto/WxLoginDTO.java b/nxhs-service/src/main/java/cc/yunxi/domain/dto/WxLoginDTO.java index 460de19..7f58849 100644 --- a/nxhs-service/src/main/java/cc/yunxi/domain/dto/WxLoginDTO.java +++ b/nxhs-service/src/main/java/cc/yunxi/domain/dto/WxLoginDTO.java @@ -24,5 +24,5 @@ public class WxLoginDTO { @ApiModelProperty(value = "userType (1散户 2回收员)", required = true, example = "1") @NotNull(message = "用户类型缺失") - private Integer userType; + private String userType; } diff --git a/nxhs-service/src/main/java/cc/yunxi/domain/po/Client.java b/nxhs-service/src/main/java/cc/yunxi/domain/po/Client.java index 678a97a..5e10690 100644 --- a/nxhs-service/src/main/java/cc/yunxi/domain/po/Client.java +++ b/nxhs-service/src/main/java/cc/yunxi/domain/po/Client.java @@ -9,6 +9,7 @@ import java.time.LocalDateTime; import java.util.Date; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; +import lombok.Data; import lombok.Getter; import lombok.Setter; @@ -20,10 +21,9 @@ import lombok.Setter; * @author ccongli * @since 2024-02-28 06:08:09 */ -@Getter -@Setter +@Data @TableName("nx_client") -@ApiModel(value = "Client对象", description = "散户信息") +@ApiModel(value = "Client", description = "散户信息") public class Client { @ApiModelProperty("主键id") diff --git a/nxhs-service/src/main/java/cc/yunxi/domain/po/RecycleOrder.java b/nxhs-service/src/main/java/cc/yunxi/domain/po/RecycleOrder.java index 6a6ab9a..7e653de 100644 --- a/nxhs-service/src/main/java/cc/yunxi/domain/po/RecycleOrder.java +++ b/nxhs-service/src/main/java/cc/yunxi/domain/po/RecycleOrder.java @@ -11,8 +11,6 @@ import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.Setter; /** *

@@ -25,7 +23,7 @@ import lombok.Setter; @Data @EqualsAndHashCode(callSuper = false) @TableName("nx_recycle_order") -@ApiModel(value = "RecycleOrder对象", description = "回收站回收订单") +@ApiModel(value = "RecycleOrder", description = "回收站回收订单") public class RecycleOrder { @ApiModelProperty("主键id") diff --git a/nxhs-service/src/main/java/cc/yunxi/domain/po/Recycler.java b/nxhs-service/src/main/java/cc/yunxi/domain/po/Recycler.java index b2c6b89..1676ca1 100644 --- a/nxhs-service/src/main/java/cc/yunxi/domain/po/Recycler.java +++ b/nxhs-service/src/main/java/cc/yunxi/domain/po/Recycler.java @@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode; @@ -17,6 +18,7 @@ import java.math.BigDecimal; @Data @EqualsAndHashCode(callSuper = false) @TableName("nx_recycle_station_staff") +@ApiModel(value = "Recycler", description = "回收站回收订单") public class Recycler { @TableId(value = "id", type = IdType.ASSIGN_ID) diff --git a/nxhs-service/src/main/java/cc/yunxi/domain/query/ClientQuery.java b/nxhs-service/src/main/java/cc/yunxi/domain/query/ClientQuery.java index 4b4bcd5..c692873 100644 --- a/nxhs-service/src/main/java/cc/yunxi/domain/query/ClientQuery.java +++ b/nxhs-service/src/main/java/cc/yunxi/domain/query/ClientQuery.java @@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode; @EqualsAndHashCode(callSuper = true) @Data -@ApiModel(description = "散户查询条件") +@ApiModel(value = "ClientQuery", description = "散户查询条件") public class ClientQuery extends PageQuery { @ApiModelProperty("联系电话") diff --git a/nxhs-service/src/main/java/cc/yunxi/domain/query/RecycleOrderQuery.java b/nxhs-service/src/main/java/cc/yunxi/domain/query/RecycleOrderQuery.java index 6106979..c0f59b9 100644 --- a/nxhs-service/src/main/java/cc/yunxi/domain/query/RecycleOrderQuery.java +++ b/nxhs-service/src/main/java/cc/yunxi/domain/query/RecycleOrderQuery.java @@ -1,6 +1,7 @@ package cc.yunxi.domain.query; import cc.yunxi.common.domain.PageQuery; +import cc.yunxi.enums.OrderStatusEnum; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; @@ -8,7 +9,7 @@ import lombok.EqualsAndHashCode; @EqualsAndHashCode(callSuper = true) @Data -@ApiModel(description = "回收订单查询条件") +@ApiModel(value = "RecycleOrderQuery", description = "回收订单查询条件") public class RecycleOrderQuery extends PageQuery { @ApiModelProperty("订单编号") @@ -17,6 +18,7 @@ public class RecycleOrderQuery extends PageQuery { @ApiModelProperty("订单状态") private String status; + @ApiModelProperty("散户经度") private Double longitude; diff --git a/nxhs-service/src/main/java/cc/yunxi/domain/query/RecyclerQuery.java b/nxhs-service/src/main/java/cc/yunxi/domain/query/RecyclerQuery.java index b5e7249..8719f58 100644 --- a/nxhs-service/src/main/java/cc/yunxi/domain/query/RecyclerQuery.java +++ b/nxhs-service/src/main/java/cc/yunxi/domain/query/RecyclerQuery.java @@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode; @EqualsAndHashCode(callSuper = true) @Data -@ApiModel(description = "回收员查询条件") +@ApiModel(value = "RecycleQuery", description = "回收员查询条件") public class RecyclerQuery extends PageQuery { @ApiModelProperty("所属回收站") diff --git a/nxhs-service/src/main/java/cc/yunxi/domain/query/TestQuery.java b/nxhs-service/src/main/java/cc/yunxi/domain/query/TestQuery.java new file mode 100644 index 0000000..a194551 --- /dev/null +++ b/nxhs-service/src/main/java/cc/yunxi/domain/query/TestQuery.java @@ -0,0 +1,14 @@ +package cc.yunxi.domain.query; + +import cc.yunxi.enums.UserTypeEnum; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "TestQuery", description = "测试查询条件") +public class TestQuery { + + @ApiModelProperty("UserTypeEnum枚举类") + private UserTypeEnum userType; +} diff --git a/nxhs-service/src/main/java/cc/yunxi/domain/vo/recycleorder/RecycleOrderCreateVO.java b/nxhs-service/src/main/java/cc/yunxi/domain/vo/recycleorder/RecycleOrderCreateVO.java index eb81eb4..60a2b77 100644 --- a/nxhs-service/src/main/java/cc/yunxi/domain/vo/recycleorder/RecycleOrderCreateVO.java +++ b/nxhs-service/src/main/java/cc/yunxi/domain/vo/recycleorder/RecycleOrderCreateVO.java @@ -3,9 +3,11 @@ package cc.yunxi.domain.vo.recycleorder; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; +import org.springframework.validation.annotation.Validated; +import javax.validation.constraints.Future; +import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; -import java.math.BigDecimal; import java.time.LocalDateTime; @ApiModel(description = "回收订单- App下单 Request VO") @@ -13,35 +15,29 @@ import java.time.LocalDateTime; public class RecycleOrderCreateVO { @ApiModelProperty(value = "回收站id", required = true, example = "521632060801030597") - @NotNull(message = "关联回收站不能为空") + @NotBlank(message = "关联回收站不能为空") private String recycleStationId; - @ApiModelProperty(value = "订单类型", required = true, example = "1") - @NotNull(message = "订单类型不能为空") - private String orderType; +// @ApiModelProperty(value = "订单类型", required = true, example = "520193382480351557") +// @NotBlank(message = "订单类型不能为空") +// private String orderType; - @ApiModelProperty(value = "散户id", required = true, example = "521632060801030597") - @NotNull(message = "关联散户不能为空") - private String clientId; - - @ApiModelProperty(value = "散户姓名", required = true, example = "张三") - @NotNull(message = "散户姓名不能为空") - private String clientName; - - @ApiModelProperty(value = "散户手机号", required = true, example = "13264654521") - @NotNull(message = "散户手机号不能为空") - private String clientMobile; +// @ApiModelProperty(value = "散户id", required = true, example = "1763507031581421570") +// @NotBlank(message = "关联散户不能为空") +// private String clientId; @ApiModelProperty(value = "散户地址", required = true, example = "xxx市yyy区") - @NotNull(message = "散户地址不能为空") + @NotBlank(message = "散户地址不能为空") private String recycleAddress; @ApiModelProperty(value = "预约上门时间起", required = true, example = "2024-03-01 15:58:49") @NotNull(message = "预约上门时间起不能为空") + @Future(message = "预约上门时间起不正确") private LocalDateTime appointmentTimeStart; @ApiModelProperty(value = "预约上门时间止",required = true, example = "2024-03-01 15:58:49") @NotNull(message = "预约上门时间止不能为空") + @Future(message = "预约上门时间止不正确") private LocalDateTime appointmentTimeEnd; @ApiModelProperty(value = "备注", required = false, example = "请尽快上门") diff --git a/nxhs-service/src/main/java/cc/yunxi/domain/vo/recycleorder/RecycleOrderRespVO.java b/nxhs-service/src/main/java/cc/yunxi/domain/vo/recycleorder/RecycleOrderRespVO.java index 2219626..ec81519 100644 --- a/nxhs-service/src/main/java/cc/yunxi/domain/vo/recycleorder/RecycleOrderRespVO.java +++ b/nxhs-service/src/main/java/cc/yunxi/domain/vo/recycleorder/RecycleOrderRespVO.java @@ -29,13 +29,13 @@ public class RecycleOrderRespVO { private String orderNumber; @ApiModelProperty("订单类型") - private String orderTypeId; + private String orderType; @ApiModelProperty("订单金额(元)") private BigDecimal orderAmount; @ApiModelProperty("订单状态") - private String orderStatusId; + private String orderStatus; @ApiModelProperty("散户id") private String clientId; diff --git a/nxhs-service/src/main/java/cc/yunxi/domain/vo/recycleorder/RecycleOrderTakingVO.java b/nxhs-service/src/main/java/cc/yunxi/domain/vo/recycleorder/RecycleOrderTakingVO.java new file mode 100644 index 0000000..dcd818d --- /dev/null +++ b/nxhs-service/src/main/java/cc/yunxi/domain/vo/recycleorder/RecycleOrderTakingVO.java @@ -0,0 +1,21 @@ +package cc.yunxi.domain.vo.recycleorder; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + + +@ApiModel(description = "回收订单- 接单 Request VO") +@Data +public class RecycleOrderTakingVO { + + @ApiModelProperty(value = "回收订单id", required = true, example = "1763736089053364225") + @NotBlank(message = "回收订单id不能为空") + private String id; + +// @ApiModelProperty(value = "回收员id", required = true, example = "533242995646951684") +// @NotBlank(message = "回收员id不能为空") +// private String staffsId; +} diff --git a/nxhs-service/src/main/java/cc/yunxi/domain/vo/recycleorder/RecycleOrderUpdateVO.java b/nxhs-service/src/main/java/cc/yunxi/domain/vo/recycleorder/RecycleOrderUpdateVO.java new file mode 100644 index 0000000..2674f94 --- /dev/null +++ b/nxhs-service/src/main/java/cc/yunxi/domain/vo/recycleorder/RecycleOrderUpdateVO.java @@ -0,0 +1,37 @@ +package cc.yunxi.domain.vo.recycleorder; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.Future; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +@ApiModel(description = "回收订单- 编辑订单 Request VO") +@Data +public class RecycleOrderUpdateVO { + + @ApiModelProperty(value = "回收订单id", required = true, example = "1763736089053364225") + @NotBlank(message = "回收订单id不能为空") + private String id; + + @ApiModelProperty(value = "回收站id", required = true, example = "521632060801030597") + private String recycleStationId; + + @ApiModelProperty(value = "散户地址", required = true, example = "xxx市yyy区") + private String recycleAddress; + + @ApiModelProperty(value = "预约上门时间起", required = true, example = "2024-03-01 15:58:49") + @Future(message = "预约上门时间起不正确") + private LocalDateTime appointmentTimeStart; + + @ApiModelProperty(value = "预约上门时间止",required = true, example = "2024-03-01 15:58:49") + @Future(message = "预约上门时间止不正确") + private LocalDateTime appointmentTimeEnd; + + @ApiModelProperty(value = "备注", required = false, example = "请尽快上门") + private String remark; + +} diff --git a/nxhs-service/src/main/java/cc/yunxi/enums/BusinessCodeEnum.java b/nxhs-service/src/main/java/cc/yunxi/enums/BusinessCodeEnum.java index b022504..baaaa8b 100644 --- a/nxhs-service/src/main/java/cc/yunxi/enums/BusinessCodeEnum.java +++ b/nxhs-service/src/main/java/cc/yunxi/enums/BusinessCodeEnum.java @@ -1,5 +1,6 @@ package cc.yunxi.enums; +import cc.yunxi.common.enums.BaseEnum; import lombok.AllArgsConstructor; import lombok.Getter; @@ -8,7 +9,7 @@ import lombok.Getter; */ @Getter @AllArgsConstructor -public enum BusinessCodeEnum { +public enum BusinessCodeEnum implements BaseEnum { ORDER("RO"); diff --git a/nxhs-service/src/main/java/cc/yunxi/enums/OrderStatusEnum.java b/nxhs-service/src/main/java/cc/yunxi/enums/OrderStatusEnum.java index add4646..48ed314 100644 --- a/nxhs-service/src/main/java/cc/yunxi/enums/OrderStatusEnum.java +++ b/nxhs-service/src/main/java/cc/yunxi/enums/OrderStatusEnum.java @@ -1,6 +1,6 @@ package cc.yunxi.enums; -import com.baomidou.mybatisplus.annotation.EnumValue; +import cc.yunxi.common.enums.SwaggerDisplayEnum; import lombok.AllArgsConstructor; import lombok.Getter; @@ -9,6 +9,7 @@ import lombok.Getter; */ @Getter @AllArgsConstructor +@SwaggerDisplayEnum(code = "code", desc = "desc") public enum OrderStatusEnum { PENDING("520192817293693253", "待接单"), @@ -19,9 +20,9 @@ public enum OrderStatusEnum { // @EnumValue - private final String statusId; + private final String code; - private final String statusName; + private final String desc; } diff --git a/nxhs-service/src/main/java/cc/yunxi/enums/OrderTypeEnum.java b/nxhs-service/src/main/java/cc/yunxi/enums/OrderTypeEnum.java index 37803c9..a9abef7 100644 --- a/nxhs-service/src/main/java/cc/yunxi/enums/OrderTypeEnum.java +++ b/nxhs-service/src/main/java/cc/yunxi/enums/OrderTypeEnum.java @@ -1,5 +1,6 @@ package cc.yunxi.enums; +import cc.yunxi.common.enums.SwaggerDisplayEnum; import lombok.AllArgsConstructor; import lombok.Getter; @@ -8,6 +9,7 @@ import lombok.Getter; */ @Getter @AllArgsConstructor +@SwaggerDisplayEnum public enum OrderTypeEnum { SH_ORDER("520193382480351557", "散户下单"), @@ -18,9 +20,9 @@ public enum OrderTypeEnum { ADM_ORDER("521999177572425477", "人工下单"); - private final String typeId; + private final String code; - private final String typeName; + private final String desc; } diff --git a/nxhs-service/src/main/java/cc/yunxi/enums/UserTypeEnum.java b/nxhs-service/src/main/java/cc/yunxi/enums/UserTypeEnum.java index 74fe252..b21fde9 100644 --- a/nxhs-service/src/main/java/cc/yunxi/enums/UserTypeEnum.java +++ b/nxhs-service/src/main/java/cc/yunxi/enums/UserTypeEnum.java @@ -1,6 +1,8 @@ package cc.yunxi.enums; -import io.swagger.models.auth.In; +import cc.yunxi.common.enums.SwaggerDisplayEnum; +import cc.yunxi.common.enums.BaseEnum; +import com.fasterxml.jackson.annotation.JsonFormat; import lombok.AllArgsConstructor; import lombok.Getter; @@ -9,14 +11,16 @@ import lombok.Getter; */ @Getter @AllArgsConstructor -public enum UserTypeEnum { +@SwaggerDisplayEnum +@JsonFormat(shape = JsonFormat.Shape.OBJECT) // 将枚举转换为JSON格式 返回给前端 +public enum UserTypeEnum implements BaseEnum { - CLIENT(1, "散户"), + CLIENT("1", "散户"), - RECYCLER(2, "回收员"); + RECYCLER("2", "回收员"); - private final Integer type; + private final String code; - private final String name; + private final String desc; } diff --git a/nxhs-service/src/main/java/cc/yunxi/service/IClientService.java b/nxhs-service/src/main/java/cc/yunxi/service/IClientService.java index 3de204f..205b38f 100644 --- a/nxhs-service/src/main/java/cc/yunxi/service/IClientService.java +++ b/nxhs-service/src/main/java/cc/yunxi/service/IClientService.java @@ -24,6 +24,13 @@ public interface IClientService extends IService { */ Page queryByPage(ClientQuery clientQuery); + /** + * 根据id获取散户信息 + * @param id + * @return Client + */ + Client getClientById(String id); + /** * 根据openid获取散户信息 @@ -39,4 +46,6 @@ public interface IClientService extends IService { * @return Client */ Client registerClient(String phoneNumber, String openId); + + } diff --git a/nxhs-service/src/main/java/cc/yunxi/service/ICommonService.java b/nxhs-service/src/main/java/cc/yunxi/service/ICommonService.java index c3bc11c..aab791d 100644 --- a/nxhs-service/src/main/java/cc/yunxi/service/ICommonService.java +++ b/nxhs-service/src/main/java/cc/yunxi/service/ICommonService.java @@ -2,6 +2,7 @@ package cc.yunxi.service; import cc.yunxi.domain.dto.UserDTO; +import cc.yunxi.domain.dto.WxLoginDTO; /** * 统一业务接口 @@ -14,7 +15,7 @@ public interface ICommonService { * @param openid * @return */ - UserDTO loginByRecycler(String userJson, String openid); + UserDTO loginByRecycler(WxLoginDTO wxLoginDTO); /** @@ -23,5 +24,5 @@ public interface ICommonService { * @param openid * @return */ - UserDTO loginByClient(String userJson, String openid); + UserDTO loginByClient(WxLoginDTO wxLoginDTO); } diff --git a/nxhs-service/src/main/java/cc/yunxi/service/IRecycleOrderService.java b/nxhs-service/src/main/java/cc/yunxi/service/IRecycleOrderService.java index 84a9d65..e76c04c 100644 --- a/nxhs-service/src/main/java/cc/yunxi/service/IRecycleOrderService.java +++ b/nxhs-service/src/main/java/cc/yunxi/service/IRecycleOrderService.java @@ -5,9 +5,13 @@ import cc.yunxi.domain.po.Recycler; import cc.yunxi.domain.query.RecycleOrderQuery; import cc.yunxi.domain.query.RecyclerQuery; import cc.yunxi.domain.vo.recycleorder.RecycleOrderCreateVO; +import cc.yunxi.domain.vo.recycleorder.RecycleOrderTakingVO; +import cc.yunxi.domain.vo.recycleorder.RecycleOrderUpdateVO; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.IService; +import javax.validation.Valid; + /** *

* 回收站回收订单 服务类 @@ -25,12 +29,32 @@ public interface IRecycleOrderService extends IService { */ Page queryOrderByPage(RecycleOrderQuery recycleOrderQuery); + /** + * 订单详情 + * @param id + * @return RecycleOrder + */ + RecycleOrder getOrderById(String id); + /** * 订单创建 * @param orderCreateVO * @return 订单主键ID */ - String createOrder(RecycleOrderCreateVO orderCreateVO); + String createOrder(@Valid RecycleOrderCreateVO orderCreateVO); + + + /** + * 编辑订单 + * @param orderUpdateVO + */ + void updateOrder(@Valid RecycleOrderUpdateVO orderUpdateVO); + + /** + * 回收员接单 + * @param orderTakingVO + */ + void takingOrder(@Valid RecycleOrderTakingVO orderTakingVO); } diff --git a/nxhs-service/src/main/java/cc/yunxi/service/impl/ClientServiceImpl.java b/nxhs-service/src/main/java/cc/yunxi/service/impl/ClientServiceImpl.java index 2433a10..1ec5a92 100644 --- a/nxhs-service/src/main/java/cc/yunxi/service/impl/ClientServiceImpl.java +++ b/nxhs-service/src/main/java/cc/yunxi/service/impl/ClientServiceImpl.java @@ -1,6 +1,7 @@ package cc.yunxi.service.impl; import cc.yunxi.common.exception.BizIllegalException; +import cc.yunxi.common.exception.DbException; import cc.yunxi.domain.po.Client; import cc.yunxi.domain.po.Recycler; import cc.yunxi.domain.query.ClientQuery; @@ -14,6 +15,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; @@ -40,13 +42,18 @@ public class ClientServiceImpl extends ServiceImpl impleme return clientPage; } + @Override + public Client getClientById(String id) { + return this.getById(id); + } + @Override public Client getClientByOpenid(String openid) { - Client client = lambdaQuery().eq(Client::getWxOpenid, openid).one(); - return client; + return lambdaQuery().eq(Client::getWxOpenid, openid).one(); } @Override + @Transactional public Client registerClient(String phoneNumber, String openId) { // 判断手机号是否被注册 Client client = lambdaQuery().eq(Client::getMobilePhone, phoneNumber).one(); @@ -60,4 +67,11 @@ public class ClientServiceImpl extends ServiceImpl impleme this.save(client); return client; } + + // 校验散户是否存在 + private void validateClientExists(String id) { + if (this.getClientById(id) == null) { + throw new DbException("散户不存在"); + } + } } diff --git a/nxhs-service/src/main/java/cc/yunxi/service/impl/CommonService.java b/nxhs-service/src/main/java/cc/yunxi/service/impl/CommonService.java index e11240c..631741a 100644 --- a/nxhs-service/src/main/java/cc/yunxi/service/impl/CommonService.java +++ b/nxhs-service/src/main/java/cc/yunxi/service/impl/CommonService.java @@ -1,15 +1,25 @@ package cc.yunxi.service.impl; +import cc.yunxi.common.exception.BizIllegalException; +import cc.yunxi.config.WxHsyProperties; +import cc.yunxi.config.WxShProperties; import cc.yunxi.domain.dto.UserDTO; +import cc.yunxi.domain.dto.WxLoginDTO; import cc.yunxi.domain.po.Client; import cc.yunxi.domain.po.Recycler; import cc.yunxi.enums.UserTypeEnum; import cc.yunxi.service.IClientService; import cc.yunxi.service.ICommonService; import cc.yunxi.service.IRecyclerService; +import cc.yunxi.utils.JwtTool; +import cc.yunxi.utils.WeChatUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpUtil; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; import javax.annotation.Resource; @@ -17,6 +27,7 @@ import javax.annotation.Resource; * 统一服务类 */ @Service +@Slf4j public class CommonService implements ICommonService { @Resource @@ -25,37 +36,75 @@ public class CommonService implements ICommonService { @Resource private IClientService clientService; + @Resource + private WxHsyProperties wxHsyProperties; + + @Resource + private WxShProperties wxShProperties; + + + private static final String code2SessionUrl = "https://api.weixin.qq.com/sns/jscode2session"; + + private UserDTO wxAuthentication(WxLoginDTO wxLoginDTO) { + log.info("login request body:{}", wxLoginDTO); +// String url = code2SessionUrl +"?appid=" + wxHsyProperties.getAppId() + +// "&secret="+ wxHsyProperties.getAppSecret() + +// "&js_code=" + wxLoginDTO.getCode() + +// "&grant_type=authorization_code"; + String url = code2SessionUrl + "?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code"; + if (wxLoginDTO.getUserType().equals(UserTypeEnum.CLIENT.getCode())) { + url = String.format(url, wxShProperties.getAppId(), wxShProperties.getAppSecret(), wxLoginDTO.getCode()); + } else { + url = String.format(url, wxHsyProperties.getAppId(), wxHsyProperties.getAppSecret(), wxLoginDTO.getCode()); + } + String jsonStr = HttpUtil.get(url, 30000); + log.info("WXserver code2Session return {}", jsonStr); + JSONObject jsonObject = JSONUtil.parseObj(jsonStr); + if (StrUtil.contains(jsonStr, "errcode")) { + throw new BizIllegalException("微信登录认证失败"); + } + String openId = jsonObject.getStr("openid"); + String sessionKey = jsonObject.getStr("session_key"); +// String unionId = jsonObject.getStr("unionid"); + // 解密得到用户手机号 + String userInfo = WeChatUtil.decryptData(wxLoginDTO.getEncryptedData(), sessionKey, wxLoginDTO.getIv()); + if (StringUtils.isEmpty(userInfo)) { + throw new BizIllegalException("微信认证信息错误"); + } + JSONObject userObject = JSONUtil.parseObj(userInfo); + String phoneNumber = userObject.getStr("phoneNumber"); + UserDTO userDTO = new UserDTO(); + userDTO.setOpenid(openId); + userDTO.setPhone(phoneNumber); + return userDTO; + } + + @Override - public UserDTO loginByRecycler(String userJson, String openid) { - Recycler recycler = recyclerService.getRecyclerByOpenid(openid); + public UserDTO loginByRecycler(WxLoginDTO wxLoginDTO) { + UserDTO userDTO = wxAuthentication(wxLoginDTO); + Recycler recycler = recyclerService.getRecyclerByOpenid(userDTO.getOpenid()); if (recycler == null) { // 回收员不存在,则注册 - JSONObject userObject = JSONUtil.parseObj(userJson); - String phoneNumber = userObject.getStr("phoneNumber"); - recycler = recyclerService.registerRecycler(phoneNumber, openid); + recycler = recyclerService.registerRecycler(userDTO.getPhone(), userDTO.getOpenid()); } - UserDTO userDTO = new UserDTO(); userDTO.setId(recycler.getId()); - userDTO.setUserType(UserTypeEnum.RECYCLER.getType()); + userDTO.setUserType(UserTypeEnum.RECYCLER.getCode()); userDTO.setUsername(recycler.getStaffsName()); - userDTO.setPhone(recycler.getMobilePhone()); return userDTO; } @Override - public UserDTO loginByClient(String userJson, String openid) { - Client client = clientService.getClientByOpenid(openid); + public UserDTO loginByClient(WxLoginDTO wxLoginDTO) { + UserDTO userDTO = wxAuthentication(wxLoginDTO); + Client client = clientService.getClientByOpenid(userDTO.getOpenid()); if (client == null) { // 散户不存在,则注册 - JSONObject userObject = JSONUtil.parseObj(userJson); - String phoneNumber = userObject.getStr("phoneNumber"); - client = clientService.registerClient(phoneNumber, openid); + client = clientService.registerClient(userDTO.getPhone(), userDTO.getOpenid()); } - UserDTO userDTO = new UserDTO(); userDTO.setId(client.getId()); - userDTO.setUserType(UserTypeEnum.CLIENT.getType()); + userDTO.setUserType(UserTypeEnum.CLIENT.getCode()); userDTO.setUsername(client.getNickName()); - userDTO.setPhone(client.getMobilePhone()); return userDTO; } } diff --git a/nxhs-service/src/main/java/cc/yunxi/service/impl/RecycleOrderServiceImpl.java b/nxhs-service/src/main/java/cc/yunxi/service/impl/RecycleOrderServiceImpl.java index 935bc16..16fa5b1 100644 --- a/nxhs-service/src/main/java/cc/yunxi/service/impl/RecycleOrderServiceImpl.java +++ b/nxhs-service/src/main/java/cc/yunxi/service/impl/RecycleOrderServiceImpl.java @@ -1,20 +1,34 @@ package cc.yunxi.service.impl; +import cc.yunxi.common.exception.DbException; import cc.yunxi.common.utils.BeanUtils; import cc.yunxi.common.utils.CommonUtil; +import cc.yunxi.domain.dto.UserDTO; +import cc.yunxi.domain.po.Client; import cc.yunxi.domain.po.RecycleOrder; import cc.yunxi.domain.po.Recycler; import cc.yunxi.domain.query.RecycleOrderQuery; import cc.yunxi.domain.vo.recycleorder.RecycleOrderCreateVO; +import cc.yunxi.domain.vo.recycleorder.RecycleOrderTakingVO; +import cc.yunxi.domain.vo.recycleorder.RecycleOrderUpdateVO; import cc.yunxi.enums.BusinessCodeEnum; +import cc.yunxi.enums.OrderStatusEnum; +import cc.yunxi.enums.OrderTypeEnum; import cc.yunxi.mapper.RecycleOrderMapper; +import cc.yunxi.service.IClientService; import cc.yunxi.service.IRecycleOrderService; +import cc.yunxi.utils.UserContext; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import javax.validation.Valid; /** *

@@ -25,8 +39,11 @@ import org.springframework.transaction.annotation.Transactional; * @since 2024-03-01 11:15:39 */ @Service +@Validated public class RecycleOrderServiceImpl extends ServiceImpl implements IRecycleOrderService { + @Resource + private IClientService clientService; public Page queryOrderByPage(RecycleOrderQuery recycleOrderQuery) { QueryWrapper wrapper = new QueryWrapper<>(); @@ -39,14 +56,57 @@ public class RecycleOrderServiceImpl extends ServiceImpl#Wt zpohMOGN59ZdHenYu7~ndLxTI4KY?u&0Kg7-s;Xq@owe7p`(K8lB3+D=Ydg9kXK`p*{vqHhYKO`8E-Uk6hFAca{=PL`w;Fk^w&`}h!NWvDuM)34E$Z+3opN2yJ)jU_p6`OZK>8r2{nHR)q|L0 z%1YWC@iV5Ez}mLbU2HUzx`++2xk32E`k0B!B~k;u`S2$o{Q7$BDpS(&)7jMd6`~>$ z)8LuSFk%=~M>BGTr%P0km@3+a`|6IDMc6ut)`RipyqePgO{8Qio9DfrYTuu1mRMQ6McY6&_y%PEXPuIkS7XTfKyW8Bh?;1eOT;GWgJ4bJ>o{<`blarX?(#|A*QyIby^}W2Re!Q$VC7l3adNA=J^!H5ZZ51)W zC2hlv*iLObQF>SVLKVMA+1NL6vuV@=#(*QHv@?^U-sAT&79>2IyE!!DFM`kFcP3q= zSGiC+=OGgRaL;}|@ilH=f&H$3^?ql+7Mt1v)hxQ*ryzKkn|zy5&L-`iv*L36UQkMF z_wWHt63bIFP%f2tVlG_=;=`Nz-o8!skZcA&&Kgr`d7?rxugA7Sy_@d?!^zO%eRmmk zWhnL$-Wb`A4%A8PNLdCx_;z$pjs{=?(-bS`z?Gw9#$@|wfa>qovTFwEP-X=Kh8{8??u<{x z__#_wn1qPO)!!ClZW!ga6%R~`dB#ktC~*hkIimg$XmMmpS3$0(P%o`O|EP|@ijwcm z$#1Sq_9O1xe{hfg~9S0o%ii}g=_GtR>XW&-ctj5z^kTyF2+|7f*w#nixi6~ zaBnR)8b>0ihvpYomX5kp#|M_FH?CUmXT|IseExo+gUz9FQGE;63guhkX!S_tlDxB*=3pz;Ua{mPYPDq=Dgz+DW~89`DA`x zJ$wO(Y45r|Av!|eYlS5$*9KiAF%;zP>gmZ&4*d;^e&ZSnuJ9yKvLby>p&(3K_XV^R zs-prBPKd!R@sW;xF{2ibPsZZ+$l@!rtIi0!SQYX2X7Gk|LvxE5NDzpi(<89~;vQ1P z1cj>87~N6x>OM-n!Lz%bnQCu;h3vFd9p#Y4x{zt0!ys*W6V?i>vy_&A2bcY*C543* zUz>N5!_>zcpIc=!wZr>7S50y{A8*&SI0qK4XGV~z1s;IwXV{t%JNQ3&>JzPcdo0wf zE8I}L{0WVTh2EhdjZpC|4Xz+%HhQ|UYqeh=2mwR^Jmmon=K_*-Mr=97H@{d7bz{aX zrFajA$$<+jShQO(PdKvzDn)|j&M6(W1o!grutUN4cS1cE{+asDM z*IE&-2RW7CR5_8A6tm7w6^qa1Pzxko;(S9qr%`q#q)0F;90<9x_(QgSKHJtgE{7Kx%`}gecaU z9-wo(G75?HS4=?Zt>82yJy~3(hoj*Wf2|Jk&y`_amSZsS+cB(Qj+HDc(_lmAx5JO< z^e5a?(oU}AWF0uQUjfYamIS=JE|ZDkB8P;FLC<>aZc@1n#R1KL=DRbHhzkrpYXQ)U zS~AZ8AI%lx-nTs}?yHM+yt>NE!-20(N@dtDLtwKvzf`KwPqASyT3iLl(pB<^9@|(f&oA|r3Q=}+YCI2rG+ez)`JtY>-owM6I zuVn4djF&#dz*F77!`gGQCMhATa!uJ(Q7TX+>@v9J_WC4hzDn%ZY88Dy zCGmJfTwE|UbX{j04Qg+x`Km|v;MdeyD8ryf4bZ_ha)U%{Gm&s+#o5NbCb`(oEJc-27~U*PDO^6#W$d*^k>V|$ z=sPi}Oz;5e8g`2CkddHbMHwv~TS&-4sxg5qL3T~*&>y0Iq}#-xL(>7;Jvu!(x968% zhn*~`bk*1ooTYv{HZQzwExFR3A0g2z;c0#20oFQ9Qup+KMuQ(CUAo8ZA%|9=*W!p~ za_X>PEA})Y&oy(k@?Jrbc@>^Ka0vjiOob^bkAu6}@kg;vzpnZt63RLo`@VH<9<;Kf zPbnzbwZl_`v4fwQJF~5Goob8EW}L8%p9jwNobIkNQ8#JL%@*`$aDXD_7PS3sZrST%$9fhDx+k)1j#>jB3H53@od`pGFh!FC5)`Fsp-d)57L}P1BceoU@g?l6& zAsj4BAWTEPiXm&o<{TkIO3-tn%O%N4WU0j5^MK;KpwBd>>}jNb$r)u+)h#Om>4~EF z+h2PB7T*Hv_?l|Qr8n#2ol9>&LOvkMAqg^bHVrf1Be$?RHyET)7?Xrc*Uw^K*Z z;t3b#7UT#>8;&inLC%>Nq{i!A-jwjb+^>j?(f0Zlceuf=gr}J+Lmw_IWp~OcuTx!% zxlKiJCm5_1`Q_e6-?FiVZFR0%?tS6e#HTd@Ufs&%FK+Y+u$9zV(n8VeORcufomY`} z`wsx({&zUbtH^KlHtLJkZV9Jd;{nNc>oxzJ>R_FrV*-e|O*ztDy(bdlV{39;CXCGg z!Uo$YN~lc-GZNy$ygpJqvV9ud7$N@%^_k#+`d<(LbD$5vtFB)jK8kHMzVR4FLn;)L zyPct3JXrAG@Ig5c_qJn0jd>ZAG1%0VPN7Q{h0a(LtCmZDE4esR8E--H08R@iue1-1 zxQo4zJ7U@9`e04MYV$jdS|IqOxY2CK>WJL)k5}Q`?r}s%B9iz9hnV4#jtbr-R4Eyw z_?AYY=ll63AtE8oG2mP}$WP9$?)>L=u~do{^AT60geYQ|QDWdd>6pPbHUlZjW_;h|K4IbHjpiCmdx1v=*g2Npa`^8vfA_0X$J3 z5Mq-{Ujqdqr~n>?TE5P{BD+q#xE9&7*kk*eS;c;->g|7tA~0%h*fa?|Fc7TCpx6rX zzjMd=!`>rn%16v7|K@Y}w$(2cU9_GM+?KE@9=;wN>5s!B>`)G31ouR{c)*B8$^Wc; zRfKeLM_G|+k*iW`o>r!XBF$!hbUJXisobXd;WUiG#f%ttsnf|XL!1-QMXt585jY(5 z@~=_^gOoKI!{SMOtW6}`g_XRAaoG03W<0T~I3`MEQ`6cCWw2=N6Ez!P7@Oh^9=v~X zHHQ$`&R0@F{N;-UI(*!t-bau;^MS>Q$Lk=j>)C|L z0fq$_9&xM3s~+vNkUTCdh)e>;-6nsO7igxc4KzjYN9a4}9=)IceyAxe0Qf1O8uff@ zD2%y+>1i7sM9kiIa`Zi{y8k4>y)*!B5jKVS#J4w1ulk1E~seUnb zJkVLk`%wRrM|!sHhs3|M$t8y@kz7gwN|}lk@=Tv5M<$Qbou<}BIoI@GJn~8UB_ik2 zLr9iGo}19C`A>jU^Nru(*CoW7MJy-0(I6T|X3OI7^%^wQcxAK>El@%IcQA4rxv>5Q zT0uIkA$H1HQ+*Oj{~p5#=58H1XnP#~C}6Nd&y=ej1OA!W2)44Thew?(u}Z~eEFu(- z1f`Q(*a$z-ss^!!oOUXLh$FUF`CLC>UO3mc1!y0k`L5$UMo+#q4qrf`NQjZUGT{yj z>+6xYhmb`!ht{C}qvLYV+O*v@jo&2TUkxyQ(LJyi?6*)aD#?=5hVYw9l_|Y$Q(|D9 zF@SRG&9$SF5Q|cWVud|b4_m)74`Q2R?nAW6b1Tz=YF=c-aVAX--7q(PwTD*-J!gcqG852e1DS(cXP`lS z{7Zx1jFOg)zNH>oWoCGmkXUIe`U>tG6la>#*{^4=Bd;_Vmu`<%K4IeM?C*v$$F8Yo zG4y|g(p8BKF6m7(xt2l_*v*P7+kV~P-TiQ*-~F&HLXxUF@{p{B^8MZSHs{`EaR?um zlO|?CAG%5m&DMQ&g6>sFQzU3H8aN))6LF$Ma0XpyM*{2>C<7-GEI(JbC%SDJzXGKM z`kAdPb-m?e1hlk2W5G-DK{8P#5>8aXr_JkUBVZbL*q} z0NX39U&P!O-YA*)85e6=0__=rRF&R|hfya!=k23n)mx-ZiXrlD`msFtyk6);ZUX*b zxQd(Ht5@1&Ry0Xll^yJ3W*KigI6|S>QIIA}if%#&_O|CZSh}Kb)t?h6JaIe-akpy@ z(v@T#4vfq9O}whxsEmdxHMxCOkiWGVT0_;#IpTq4K$7hfE9*q>vz6vMa?H1MLcfaH5jjEk^R zY#7tA460-xZhBNTxW zM1$Wx99!n9PZ=V?H7YUpou5)O-GD+~ZugXQdAD*njU=z1qR(II1@G#I=tM>FUdd<+ zt%;6m)vEC2r?70rPk^RjXeB#)Z!rR2FlBHSm>!M8>L(7Y$&Cd}-L+J%A+-}2;)Xw+ z0M9EEMsx%<_og1!iu;|-lvfgOsVFZ7H|>Uh4ixWYMK-^Zj|CPbuOr(_*a9M(T3OpV z%eWNT>J#%M{su=iB*^GrXO;FUt%~?-_+R%-P#(@6H-T)wPky{eX}aNBWwvisgOh@X zNfS(nNGYsm`y1=P5VhHnyL4@eIP1 zdZlII3LRP{j#m74tne@E*x}~2`w~?MeH@0abK`#UXO3)_)5RKJzWO~9t6vqhYna~a zGMcfk78K%pJL9TT84pRyJoq-evuY@K`*1yDKW@G1c>TfheleZ55AX{6xU-k8EbA?+ z2|A7}=&5Ks9W1gLv97kpz2>15!Yj3WuHj867CUZGGEe9yG}x(kKPKBa3*wIum~;WG zt~@!RUnAq_ z+hpP;cd6cwU0-Th-b#;aRxW`^WzyVq9}+C^;E%e*^h9%+x|i3LVk=mq_Ky8jsc}P- zn@iSZ{5Nt=k6vL7Q1K6<17(0SNayLz&7ap2KjFpR;ppYvFQ6x%KCs4bI3C2O(SO#fy@aDyebJKePe^2GcM?%h|Uv646zVNZuXq?K;u7i_-(A{HyX}3!i>RD76 zC5f_fMRi)**S?kkqQ&w)4=gQZc`l8~ z-@n}G_a6dDs>#nH%<|{3H@--B*`RWxEWDYoHqR5ES+t+nPlsAz2p7zBTu>D)x%MpR{407Qh7ouYG=_dP&;@pL4Xp^s;Hprsxj*eR4ea%c=_SSxDqmsUotWQNq2Ep=yOjtuZh4RGHKX1A`HO^S`%KLsC37%>1WtCeZaC}?J=Al(gpt+HU|GCq)4s*lHe4LA;(EQ>eJ#e zbynnml1tf=Dt9CCZ|K?-)b-*0>*McqQuWmjsVwG>w~+xD^Ro~Ao=4`Zo_$w^h2ywh9dqLqfD=1m_Cf<_u;YDtH*vWQ(}x8|eUR z3sMW+0>zC9*?l(yf8WyiY)Zg``Vk-z|LXu$uv0iejPwX0!_0$PJYO(j*eKLYqZyj@ z?biBH?7=1Ac?vcQ!P8|7 zdANGh!}C-br&5?aIbcoxuWKz`x@3hB92wk!j1{ylay2!ta)&rEbb;NcPbA#)9=9b@zS!ow%-YP&zb(gzUK))FG=ohlU$i0Rp~{4Jq6o&7acvF=anCC39z7Y z;CHC2fN0Nk4KY83@*HGeKxaFk)CJTO1#K8oj4SDrS zwIj8@bD1@d#@Jg#vot>eEwQ}>Ts{woZy+kNAPYrP;Nal4MM09+kUj2}@d7_ygNXOv zwOU?@hlzJ^ zUA|TH*k7VnRa|BpIn1q;0#iZ1_HsA6fvWjz#-HR_3*A$xF3xizQDB%x+#gG(46SB0^RTUvoG)VFPTa#giV4c zXcLaLXqL2A4PZjfdx&3qP}8+PL@5_*+eb5?2G9Dm@I)AM4hTnLVjJYCtRn?8Gb;s4 z`_lrAuB%n_^w1BYxol3_^0PB?5UaOdi=!O$P5yXPC-IY@3Ae`q?-9_Z@ACSudqaiI zAZ{SKp+C>s%yZ(uU04`HR!8dF8%nXXx9pZ32rH}3))+5rHnb2@y7&_qq-TEzes6{` zZ#{6Uw~)658MVzSmIvwvlvUWtMN3}KW>xatuljo&_Tt(0zV2OTELwMVagDHoGkZda zmi}s2$KN(Kp$!qPkCgPc4bw4>3)i^scl{C~7plHv1=GYV|#fZEEbdD|z8sW3p8&YTk zN`Y?E|N0V1Xrb6{Q*d{2G+x18A=pNc)zJ+$Xj^qffMWhk-~_LEpV_d?;kx62se=_c zi%Kcx3CcY0CDs24)n3UgwfVdo0wXDC+nKad(&^Za7yK47Z{2ddp((_B5h+Bu4*Uc0 z*qB_7DUoRJzs}R5R%hSr%_oZwVX)w3w;AFQXx+!7AbIbX@620PBh|-h^@nN9EB#ZK zS75UwRDI)f_l4yA)V$?DgIVz{nE#|h$ubDCO{hojMqFA;++{2x)6z7&OIpaKD7uQW zucMTMRK({?7Uy^WvT6yG;0OtMc;p zaj&VRl9xp@Kou1oqcU`EsG$`v!YK4Wg{Eps5T?x`N5q}&qfa}&dJ$$^Zuw`siG5{PH_{8okSP3x9rJS*?maw_Q4{lTkdOj zJNxroA-ObJr?Si68H_uo8hlX+*#f=4jV1yT;@X)w z+xypWJC(L>Em*sflZUrw>vCdxL`B1Ug7hOhRoS9{WiIX>1k%$R($ApSf8Lrxh1IZ5 zf|uv@-x>i&Q93n%NMx^SK>Wup5^tp&P$|FN{(RD+48%x`=In8p% zuroQX*M(m+8!`OUS7(h??AlcR6;UBC#bEcnuHH2(9T57BdG(G`TFRMK2WL58wEZHg zEkU7ykJeNl_mv0Fi_@r&Fy3WPFF^^?Wk3%d1g*x5xhkA2t7v^_qS85TUrP^T)sxP( zKK-~Co$wnKW|Ij$*WD#=-zEHDHKRpXwJ@Gdm zAq?i}=An(fmVM`poZ5>-X<=$)AdvzS4f0$pL4hlMsJ*Q7ND1i#bhQjea_)+7f4A|C zd9V^h-Gi*}%>z-Ys%AGrl4iM`Lln3NM>tV9_F73Q4;aPr@;2&g2&D-3`#Pig8$R?g z?p8VOiu6ZyxsQf<=bC?U0Rfm7(Kei1SQ4-A%3}io<4k~iU#jlfPHto+C7!&U8>Rzk zgZd7+-Vhz_5A3@V)E0!2Rz=p+H{bAs4x%$56&_gEd-=uMoE(tbs9cu6Uyw zuz~8!CFgfz-|MO>-mq$??@B~$;vYz!@SM552i4)i{2yTAKsC-Is=D@|m?qs70$?Wl zP6?hNu#_x5pYAojMoE1w6I8Th|6O3`o|fibg#6iN=m(n(j{T~rn|Je`!HnCRy#mUu`d#}X((G8a++tu&p+dksRSX@xExBuJo zQyP&x^43LOD;wkorF~xCoFZN$cZ%GBV81W7gaJ+@_>wARoyOh)N_C^s0ZP2sx1}LA zYoE%n&aNB}9M6+qqj_%29uxRPn3x99MR4wX_))KW{P>fL2|H1l??W7#zS{<-{}u%W zmSeE)?HOP@%biuYw?Cts)xMsM*PA=R;_%x(AJG3U3AO6DNS!d&IQ(f0>(|t)t{38c zzxXS$>^eYSQr6S{QK~Q?s8OmFRMKfyQ>}W~o|x*PkLv+@$gKKsi%%&P{1=yS)!|_z z-Gx0_74Cy{ww2ief<3oz$WOU7qF8V5U^$NX1#&JZ)5@f1fzm$mxUeBVxXYhaba~m@ z#WJ^#`?vkRAs1p>%{7Ai54qsF_Cb98Slr4HdrWqkBlxo1XnGm)a3Q+0e82C$6=)c8 z=dd&hz(g_cPD9g&P|9xuYu6mzKr}SGqq&m3ZvuPfTBdY<>d5PTU-Ex~uN_3&Gc7d# z-M(eiQWW#h0Oo_No&yDu`M3WpB%ARyrwoj^G<`5x9Q=xX_-ZJ2r_Jr_U-LhTUFTjF ztQeo8uqIuQYu5c>JUKm2aKB{OQGY7Y0i2i5a}Qs5B0zui95G@bpc-@W zSZWE0LtzQ1wy$loy}H6X!d!lvSD7-I@pw;YvhutoBI(}Xdh!__Sw9d|O8j0v2Lwmd zzk4(er!?yiTUe#?a@9ki?6_ZO%oEeN*DBQ*6X?QxVi?z9L zvYvQ4UD(_5xyc%Y6_=m0;1}V`Mz)UIKL3jld>Mr7o1qAxApyLx(t7if5^Cxif75Fy zU}fZDaKI*6+NJXOc}wn_?_HK1V>9fNVUakV^&@Z_xrw)=uLUcgKR5`bKvM>8wMmWd zG)?ywbYiv>ipX{gw?r?!9mq337UW*SX+In%z#g|I@Cy8?cROqDh=D~d7rmW!Z-T~) zozXR1UeUu=eP0u2v2pMPy^^8jU3v0iZA03@P(e@d$eyfzodmBc1Bb=ILRWAGFdJa! zmn_WxzS%kl_lJUMR~?Yo1L7Kd`~BwI=~iG4-TDdgv#&Rmd!(noA*(CF{D@mkB9nnjdy`a_Tfobqd*`o&tO5^zW0w4V$dtT``0@|d% z#MT5-mpHN`m_IPT>F%|gF^Q9xL*tpWlRmM2@hMovlB}w*j+P#iNo1m-oTYCs&29`; zv~x}a>Oj-A9ZaE`&Nhk-zW#o#3H1X_y#kQ-`L=?U6i?QQ_{k9;ihsG`@imZL#t{C1 zOOSW25u4wq`pa8Bs*4*PLW{*@hw;~k!m9ZGuYBy9Ce$9$zMJgpCqrp3tCcBef0=xx z)^_|bOr`C6zLhg_W}gZ6HC4>|@HvI?n3w$Cas`6+KoFRh3XMOU^0Hqx!`D`g)Tn8+ zgwP%ffuq?o5n+5V`dPBwy~AZ!wMfTsCIxRyLlbm7ICLS6L*;KHS+=gJVZzbRH%dFm zBp=lg?Vg|6@2}X;qMcLclY!IFh-#{ABxj174~EXIkaI9gx8_B8uBd6;G0u2%-PCIA?du=7aaJ=@T92R z&r8v}hag|#xN!~VAr``WCzPKfYV{X*0$Wp4A~T!dId<#dM5GgG6=11-w-<9=;` zHpLt)RMO$7jsC;=Yk&>1yJC4m(msg(kmZ0n+Wxd9;MqNO_iGkAx9|4nfhFK4(aIt< zK(3m_mE6?E+WlxF=$;YVK8^}oSdM(PLlAC(s4QHp5?rQ8_iXMjn#Ut=f`O3j(xVXKk=0QKL2VUc;$r3M%+knI)Y=tDZ(N` zqe#o)U^$APbz?f`YEBv?dK8z&kt&Yku0g+*A(>ulj-2Y?k|w|LK%kp$^&@Q^YwC)~d9GJ-$~?R?-fOus0=jl>fP%_cOWKlSjs~TK`<_W4ExsU6ZYm- z_mzg}`3pDNHLIzsXI-!NJ%5&_p@={Dk8R1lPuW|>@152aj?OUlZm^oN4yYQ9`3L;m zR>&EKczj+r*S0=^@^Q!~C%yi9E4pLrP711%X28e_<@~!(;$ySgE#hJR76%8ii{wG2 zZeIk2C^#z0N2p?V?-TifB~8Y2%c@pjNe8Jr6cd|xc}n7?7NJ!MfMYK}GWYI1%EnBm zUNMSzB?UE(tV}2!zBsr4BL^J+PdYDjxUG@YVeGk&%AsW-Ro4ou#uv_PqaSjg&Ww?M zGx>}6j4lpLdm{n5uT}~KFq0vlo@z(VcGZ-f($=&V$DZ=>quuIyq&`L0Aw#e#`!lV` z-jR0HhygG;aGuOpT=SML03=eU;khi|QzjIpLt$T+`^Yot)D&aN4NFxHl);GTMsj&J z;&UD|H3>+^J_*E|3+-l>_WICm&Z?UCvkiyQ8#L_7s`&Y@qkXrzjxy0hgl57za0RA@ zeJ;cBdqQvNWBt~3aqs6}j~rz#-$Yirdc2j4^Y&+#-bk-pTl~iKq~+`T5ZP~mMF%N# zR}wYs&tQRNBoGcVy+CVU{DUaktOeeH7v0oP@^BNLmZVEItY4X>A#Rv5#cFXZKCKNm zT)!Z1(D3!@XqEa$?LzyZrChO7q2(TR+y_$u{BIOG_rG!4m~za@&Efmpr}9sVj)6sG zGb=IbPr-Wq$3$de2yTwb8FqsVp34q|VHH~6@Zdu0TVedD|^FI%dsf0U2f8D-)WD z+Ia=U8QthflIv^QPj4s4{9Pv}-*oy~UZ!-H&HeBmNc;f#tuI$J$RPPFw;DD}Lb6bD zXL!|s%XlK{YaUEZvk_aV#+Er(Q2M}7b_4XCB${y0zFtR~Ag(gSS7G4SH7`fK1SCpW zfbKh1#SHYL9gO{OU?n0d-fRY8h|_j>+@MbIWk4LyEhzOxF5@xjW(bG(@E-QbrLwl* z1WUJ-tN(9mb}onR|B`utO`7cze;U5ukIwHWE=X5=^{nmIS?meue<b3>k5fq^whC40il}~iE8nHifRO#oa?k?ho9E4)_3S#ED~olD-^OFacc3EF zPS3~B&&+MX4!Xyr`-NV@4G5V~8;ql+Nr0D$-shF>(n*bSi{ZqV#0GR9@v7>llfaL0 z2{28w^<_t^JGG$^*$OTr$Zb3nZfc+^WD;B}G53*0QX<3rMHao1LuOfJ9r|=4he|BF zT7`vSKx<55jyPu$+>2Kg_G{J*w{Ry3;SfE+ge3omzPX0(nEhfYfER!2n*RET{+-S^ zSX%xOWWX!^0`|?%r(TvX+lx7ooF;j6e;Vvf=z8L?d*ek)^ek^iR{J9rUbUte50a2rpu zbXUk(VU?o%&)d$ArLErgdz;X^nPS-$1z;MJqq5^iud57QropUnH)$+5|)< zvbITtEe;C9OGaOQOU&iLA#KgnS&kAY|`V{{$R4$9}gvla^fbWQTRE)ah ztwt*y``xJQR+m&CTMzDwhkK7Vo{*P>!i40so0y|U&JkZySt8J1Pzb;?%v6Xe3K<*p z`T~-F?`2+qzvB`Tk{oZ!rAV~Gm;TC1n_&xWFGI-wg}j1#09CVQ6=^%bSMV@r{4khg zpky1I#vWdEeBP0Y1Mux^=zVKnYeTNy8@)$1OvqT+!KbEZR zPW71MZUybUIkiYahTe_nBnoJr2gkA}rzR@vZV#l~JIEiHOIF_DYXsn5evdrjPiUKL zOoK^4G~!HuTJ8x9&J#s%;ox|jJ;e+7O!v1;+G%-^;#rWkc_8iMqq9Jx`2MdfP8%;? z)PtkI9*^I6O40pg_%yl1?w7+qvQalPD%4WK5rm>W~ zqJBxOJufd~0hdmb-lW4uBxs3vTT?r>PN!)o{e}&uvk0UlMk>>>ItA(s8JA2|;xmDDwyHpfBT||)b zR&`u@X3K^1&{Vm2g?oNhO-(nI>MV9C2k#3G9Jg#8{X-G4+Y-I_0$WyLS#-e}vow|X zPuKZ4goCLEbc)((5w+ss5|(}p**Y6&1(Yy~mD&G+%u09`a;5#Wqv#8oIe2+b+#UvL z-LP0F0-J*ZgXi{JXNBKwSK{#dsmqDC-p^3y3jhC<$yqke2`N5B8%1zI;g`H;$#D_)`V=b`nXu#u0~rkhBe26G_ef37@kNAn4e z&gAVt!`7LjbszJ{Ei+m5tHH8ZQ=I^5-GhzH5x=(SHsXKNOJS_^(b56u)4xPtAC=u& z;r)f%V~Il*(X%~zBf&YI_J4F{e}$z9AOr*6OuvM$YE0Bpzu(>Vk;Wp{s=#z#qJSx)p%NuYwJM%lx@d zy#o;9+g#GEByr|z7LO8eZ6B)j3H&j`UHK?o3DbWZ?n>(6cC5t~j3Tng-uVDodQ5s4w+GGMXyPq$XWNSY8&2r?kz_4G| zJh5zueR!hs^H2%$k_)(i!j9O)C)@ly1f@uuaq1UQJO4tFT&nmJE{{W+0o<^|Hv6k9 z^_F*^<+x7%BcKBC`|9o^I}D7ALl(5-9>a}84kGURtU1$279S4$x`z;gEvK^hl*4-O zdVqEif`z0@0mm})ABPE`_d*&52Nc)p5BG%Te=Jltas1%%j@ziVj_=M(i`oi*r}PD7 z_2b-!fB^Umu6&<`#Cn;~m32rMPR~L;>$!M znvF`h9P{FdhsTD!eXFzA?eOVl$FHq$=I`-?nmivg56jRS*FKeZtdwD!#p4toScez*&OY-^?SLw;z1U6=7gq0RKpum~(Tdd6Scmg>T@>kGgS@yZwQZUunGzJ1zjWAloa8i#0m_+$`$KeW$t!I-#ap z;?R?}cfGOa@qyZ2vAQ9ZU@a&M5wGY|K2b$tLg9in5-*8x@~l#MS$?%D@!U6Pl|h64 z0Os6eQ}MuDHD6rM2P0M4Ue(a;y4(odU(%o-^#ZnEXMesGIcj}I&Z4jw2OK!5aIg|9 zb|uTIa|Blra2rH!6__DY3?Ao1=rC*5)2sGSTbr~Nh*V%V;!FO(Q5LzJQMMGQ>c)i{ zx>X=y_Sbnn{}zQBAdgplKdjK#E`GukKo?!f!A^*%Mi0Ey5Wq0ZS#S6MXt!dOt9R^^ zk%7dHzu<ZA!H0m2i3Hsx-Gp4;-;Fi;bEq^)~$exmzvxGeKzR3cdv{Wmw_10SEawa~KPX6VhNs-+i>wQh`-q}d=lCXT- zMMML623%egMbVHTqI)$!Y5=taw`NUz(t&IaYk@K^1ricYY|}j!(y>DU5}{1;|NMXU z_#6Szg{3-hOGh%k>8(8p88*`O*Gp!wwwo1RQ3%(WfjPaKx?emUwA(&qsiw<xLaZhQd3X^mpcGsic42!zw^s*Fx=lDtq z#_Jee_gqz<4SsS{RIWIL6d&<@s|JI{_2fNC9H`WPbe1;DLr&CX?V17w@!TG6F?gXp z?n2rQP3p2x6{Wp$L4lv@A-PV0n}-0)pf;5K2`zzZ`lj8@{ofgMwNbE=!P}bA(|e~M z$wgF!ZzaoaS)04#)^Yq};K!W8^`vd9Mxi$we{=bdz&{#BPtd>C9JQ89 zaN@6JltdlQcYhU!=f-$Fz?qUSYs|^#g+h6{r$tS)+q$hD_`>m+BbJjVAS{XgM#Ah1 zT}n-N_YEoL3QRcX1ZxINOkHxrCp3H#eT^{Xf0dkr4!E~ul4uy-vP`KRD8v=-j%*#i z+)BYTi$J2j6ivEp+qy8qD46%X;a`S6zMX6ZIt1E2RF~-_`w`DAMh}#ioj*J}xd6F9 zmJ0godN-sFX|$br`(mvCx8%Ki2aG$I|l`bBWhaJowp`sE-x`O@RJ)IEXL zxTGYysGIEYiV0L#eR>OFu!1nN`2&=15Nkd4Rp=#nLDo4XEEiYs!d{y*7TU1+J&I1@ z!DC;HYLp8f?`qu`ho^n*_!|QIOYsxqkbgNF=ITb8oI*EjuLTjdLCtUgy7o#0MC>rWhvZ}J!=_B_8V@*gBT8oYX+PE4Hk zk8bMuE`D;b!0E?^KyM|_U|e1+gYV>PH=y^<58tUI!AJ3g!U42bm&bf6aXa95QwCBv z$sZ$niV2oXmhVG&b1V6L@T&F8T8EiQLC?48*Ajr`b1H>e^yoa8U7@oDO6^=m^tB72 zxi#&DuAXY{ah>_pg+rf{T#CL~M3Diu!7NeBKDcI%r%bFy~qUIp}``ZDcLagK%h z{NE~R!Cusg-v8t2Ec}}M!+*Vv5u>|dlyr)8jF6B<5D=760xA;H2yAppNGsh45~9+K zkP;-M1?ldN!N%G5ch2wp2ix;{o=@EO`?_vhW$m!nl(M|vi8|B^IrD4>1I|?Ir+quN z80qUu(-z@Fc}?zeJL-ss22J-ck}NhP38%}87Sq4 zbf71hn{+BU(0Qgwae7Lkq!by+e;v;)N8YFpZ(oG(%1|tl+&$DL8nJ1VqW@|$ z5!DQEOENcwHr)2mU8M-~=VhC9LugetRiX5QYAL zAaQZOdkFd@-DDWcV35*5sVG2k#YCiISppk>w^oxM)JdC~7mh47ij&=eG$@GUQHOA% zIK+Jm@$NcIkaXq;Si^Ox0!BMk?eN}Qu+Rm|M=GbUyLXz8RUz{b-RI7Au*xniv)%^M zH050M!Fd{5b}yldJj4iOnZD>SXz20Y8how^%z7t0JW>u5A}JQ?9JnaXs~O)so*zfR z-!Gs00#OKxDFIG~rw00M#)&RdhYbL$&{pyKGIoYh!<+%@Rh4Z+2@vIZqhmK{X<-?AVG=rh35G7>@VvHW zI}J6x|0xA-_cYFmRv115#_vQkcrEdC5`PZ;?9fjVavwlJQ7ph)pbbio&nt|&uUeMZ z-2KnQhKW3c`gnyZ6Wq)(XpneirIZGTr?ZHM?R%^`&l-!?a@~Tn+v%fB)qAVPIy~7oJ3J-B-#EXoB|_3dLaZ?*8{Ai27DJEc9se885l$%qd0LeCEv97$ukM<#J5Warq*TyF_Pf|F$voc* z7SYO{7f}}S(KDxYK*H%p^0Z;+h?|^5vo(q1kBm7YsyWW9u;iLEltl+cv1e>9gWleK z0UL$Uk7h!yjL5jm~V5C19f#Y>1PnG?79hl&_40`H+ z8T7t9nJcW8Nb?kW(aIbAq$cQr>>4Ep%+G z#7a{u)b)tR!R}#}L8mNT!wzn}W+;uwa(@#qNv@-?eoH_1zxsln;^X78sO6y?sz;NH zA0_|h&9BA&Aaj2k$i}P|@b=raGo|lXHx0_|!4sKqC&m95eZTKlI!Rg|tk&jN>;I$l zb0uMyMX&m5x(&6F<#!woV)bRtKC`WnjPozC@hLKj36Rnel^a5)OVWw2PZhgz^laKB zI`WdyH{@RvUK*4p?B!?NO(IE+RQM=$5>=)bm0WM|KdZo)ko$gRytQh@)}B(LN1Mk# zrT{!S`!5J?aGis39__mm6m}>Hh`qg>JDI=5HsI(8{Hy;6Ud)!CN9Yu--u~6If}Zzm z_v^=6!hG?!Xw}{%Ny+k;q&ci4#YFw;XTuv0{XPz*ws=nb09^j;&Pg{U1fbTS4J(j- zem1&{xvcCRJnU}}Polw<(mN@;;XGNv+bY!Q-B9%KRKo|J8>P1#^Ioo zN!ax}j3naboS-4`X{e22=tOq6-*`d zlIyl$5Fwo=kmB5yIhkKVqKJ*Y#Q403lE04&N|XC|ger6#aM-%8$J7vj)QevBDvJe# zRAZ-Z3z6*OrW~uuT5TK_Vowh!U<7(Xo}xytV0{h&mam2CHK z;}ora!YjnyCrr4*uJu)DEtY-QwWhw|h@pdnHcueiMUOh$y5x)nGyB*)B|xM1uWDVw z`F12VZ#4po!uD6*IdAV$Ko)}al3;*#Z0T-spS-*s-qaP+l=kmRD3xw&uDPE%phQK3 z3^Gv2XWw*RFG=-@k-v=Wc)VS9*gW?Xc&h>$ts=OgO!#5-EhG*ghj-X_A{8PAkYtFy zo?fn$xI}@RZ{_wPG(rHXOhcmX-Y}z;OPziYhDX2sT$jG~L;)PvgiWMq~}V&Y-^vnSrFY2-u1PI&yY`a0-z zCv{!qiX|*dTP(Tw^ zzv-xod5^M5m7nlbPbo^DjIt{(pUqOL1|1)#;g!ijM9smK>e+5y6d*RIJWk#IuiYexGA8OV%%0=nXgHf|0@YbuQ!my z<)y5NrA7Qd_1J(w0Iq2vg5{>p>w8wA;j5Mko3{UM&wLzblF-T}Dd11YdKHk|H^K+4T#G;uZxvZVj)V&*SW4|DdfyUpO=PRiu}gvr~g+0pUL1*X_s3mt&I0=asETEIcYN3Mu1&MuVV zaT-!tW3Z%hXe2%Gc{RMwmbHXp+1dZjS;{>5!B0qSSB59Yy0+*j4=pA0p|poq*YK4C zk@)ZQybA0A<8QWMm2zc%&x~-WOt_FquX~x>n3zU5_rj8~_-v$yz3BO6&h-(BLl(&Y zPVQeGNuFEO^mAjdQ2*#x$xo=<-S%bC3m8|29T){_ zy}JeBVrn-c-TOCO)3f=Mf9I38QS0PO=hd>UPo=K-ENF9sGoVI$z$u0B)m{f+Jzv)H`Fjt?wryI#zBe>+}ERRY-Cj z!Y|ndNzS>`Qj(D!smpjJ0XW{=H+w+z{vtNChdf|jnSY|(Hc=Jt%P`-9v|qk2CCdPg zp&oqjj^?Al#P4=1O89Eg>*)@Yd(9^pVQlZ(>nIg@e?J^9EsAl#yEY#jDR0BYRt>>1 zfkiuq+Me9?8Yo~>aftda&h9#n_20}ZT(`5TN=n9e*FXQ2iq&d>tB#yH-{vUCE*GJR z3pKger-}`RDaeTS5Tl2jOXPgLnW$vb>U1T;$-S80GA?Qmu;BMH29;jJ16+onp1ZA= zyAPLU)Vtt$?WrF3-C`E2{yAASfuKGe)%h>47}tz45=N|;@#2Yz<_2#fkitpg{N-ZaRp<9 z5{RjUS9T9LzI)J7Lb@hZoxIBd>K932df(ZK&ot3Wz|5w)F_k28S>KxJ>8E`lTkzj; z@k3C@nWxG<=Mkp;7hD*oed$Q*0ZPQ-q)Ky$`XIBvSuck- zywIM`801hK%`>A%D)&ylM-}Rsl!K&0`umUd7(Uk=-PUGt50ERX!OU1#*ACXIGv(q} z$ZDsI`uk9sVB%LtPRC+dI;H&LI{jau_}A@Tb=vhhk$tG;&aLT}$6v{Jb>_$xn&g(T z_y(6{^B5RUyQX?2{PSuXLiHz%0s_}IC6Jw3q>GW*i|9w8!iVkw#dDVv@bg2G{X2s0 z=7d^X9j~rvmTsU8328m_&M_>>gJt>Pu~mmq3l<}9TzufOjXY#xC*7;je#PjdTz8Kc z^9maM4V`1mzGOwk;%K(}9F&e+I(O0l4(Wu8ixGO~ZWT^vXk_blGNGi{*d6+`I$fMJ z>6x<5iXIT{L-C)HQ{QPUBGcx$<=siyj+@2}jr^7#PR_F$6N1CFM&9;C(S(|!Ki`_4 z$b9(A&@PQnX6_g0Qg>T2_-SAu^pNEkRx|Uq_(c?-0g?nG2OmO2t`&_$rw$alus;EH$okIcDh67hk?=7`bdAJVSy#zJp93EGodx{V zyNgB-PF@ZvTy9EsW@mraxeP9$85VOy7>#L4sCf`Jj#hX*;xL^vmT(#t|9xKVjH-O% zBQ*ZN=H25vGM}RdX^i{dM6anLhJ<=VHWK>M5If`ptZ-z*WyZ!tqk(4Q`^fIZ)cef)c$k$;rfcMi@LlYF613Z09%KgjFHGI^v~zSNI8o zi8$mP$nwpm(d6gsH7_KhDI~QLYPQ48GS60Cr82r07WsLxNnY}7{+?ccEecoQG&H_u zWm$dg{(!Ad5N4<_WiADf1*aB+VxE%Yn_LWLvPYu}`Cz_R%*5+Ol~`*E;mfq_vVf#; zAq!VmA}`i+mJh=OBC`aJz&3ECTiB@;r@gVC>vpMf-e}ET&&sd({IWaOJO3RDg?f!0 zn1!ujr40wW3-GgDKQ0^E$rtctfDhBuS47KBJ``&C&aSj{#6@iUDlhMs-u$B8uY2qw zXHm5{@mvGW;PHROa|^lQcEH$5^WHgH?nx@Qy^aE+biaNGK0OKj#eTy`pF^}*_rp-+ zUV-Ukok`Hp7<^RdUI*kK&!y1Tuu|w#4$UlM6p57`g9%I-X~wX(dz9HR!~o#M&aJ9q z6{SoI5He@JiUH{~9dFNH_V|RHMV8|ICc5uEM#&FORC+lz1NObY`}-n2q*O)Ono*AU zRZZA3sh*bTPbts+@PUA53+Yk79pH9MB3fc?lR%7rxdWf;^!b|N)Y(L5?%p&$&nAJk zWGvj&n3kDCw*B$=%TnKMp=W^#xQxH#>LRrTo2Qe1$o)Hgq!?W}JXZDC@JAna-NXb_ z7uC6+QZLAU2YJPX z6mj|ibC^l0pNCq$sU6o&+pOcmI~x2qa`Ax1;#$Pt(V;0Iro&5)-#lDOQy*_38%5{n$j(cG6+f0A)Z!-T&6H>Fk#gkW|NHb2w>uOB_pauZd;1KerF5}&bc zv)I(#-aO8KDg*e4R?s~=-IGO7tG)Kq$2)(b#o?9dS=5wI<6Fw^eN%lZkRp;3j)IM_TU za_Kn>1?+D8eQhWVi`FO!4}=kzQK^2Wr?Kx`)Ap)@y=c2ilvKU}i>^9~WC|v6+_5c; z9#JpTgm<-WOl)_BOl}AR=kentBY)-Dm-E}u8y;X$fvO6%j=g^tcbxi|_NDe$&eR8# z=An*AU8`0HBrtK|7u0`6nfvQI)ok(5*9c{0dVU^l_{vK~V2`%B&4VE2BU9OP5|Br* zBMX37n8>gH?HU0VbVu(5`)tthhlMlPs%_d>d)pIDV&D|KKo8f-Kl5CQa7o$?6NKq) zNVg#Mhl#YPBLgr^+`1gH8NQ``vS=sp`SdiHlZMx;YZeq@zlqu+84oQys6Mo)=~6XoC@>hUdo%>IehzlpP^mDvHVJS!*Yoly@f1+XB;NLk-YEQziH_#EYH?iZ*iZT z#$cEJx+88RI(KoK{C@>mrEy@K&Q@WvJk8fWTzEjo{n3&}0K*w0Nz3h<>SVAmFt4c~ zX8E)EHzGLhFH!kA0ZXGmX*pF83vE!sHs-&8=w={wsqg{LaFDZ}>*b6D5kauq1qqy%g%)$t8lc8VwE0S~J}nmIPk-4s%k zMp128q)Lj}z00Q-oPYj3a5F3$LnSeum;4@yAm)O(CDf2PZ^E4o@$KquH^F((0Rjpk;Dd9g8hp=@k(jKu zq{&sulfjA5^&p$f`8S5&?h?e%=iGC^yj>dMfNVyoJ-U3ma0cOJ3LCYpSD7@zyY7gs zy1s$BKfXPUa;`VS3x|})WP53S<-ps{v8jTQ~H+r`%Nn{MnSpc z8k7AdJNdnqG)?rr1aJY)FyqKqb&RH;oV z>=(ns9|5h;{y_)0C^vlwNeR9yKvt~CD(;#w5xQ4mMnVb&xMoJdmyzk?j=bfBnajaB z41ZQC09SIoacogEHeuWCC1-l6(pE1tXfwHP5@mERk|T*{E11=d1aj5rM+zCm^gh=r zAV$y0UF7%Fk!=o!sI)%#M-b$R{XEv-q-70EHm=~*3g~_b`S{08mFez%#ti#~0hbo> z+Sgad_t8DB*{pSxkC)gtxOejfj4D^-CrAOx?@p>;x4zLim}tu)|M=k0XrusS!}u+n z<*=EpRyt;0aw`hbu&TNKth*jPXPs!yYT`C8#tx^*pQ`yqH$bvW=ML9oLa8%LqZLOV zf1`EW6A$#0+r~{P!dCM+CoD-3 z$`NNp`k;TMj@BwIT|Hr2J+J|3%SPYH~5&%kI-49N%T+iRDwAN8a2lS})=BNH>SL4gSS*HHr6z zbUzyF@cO<3LmwG&w*idg^=$k6ZqVV6@Apt5g<6OCb){wbXgx{K#)0{R4lYfV0B-ih ztkcwPAmQY4E>`Cc<8P_z z%PqeyRyq1WsHd7=YGWtVMGm!&=?kk^ntTTT@;#izt?LJ4af^MW$~x-jN&05VKKeMa zyCgq;v5=uX9AVCFE#9+J*6Asw**jfttL7^R#Hn#ELQ1wCnO-r=uQVv+vkra6?Jvc+ zIiaNKd6Mejpdd9>93D-xCNPNRnUPX(Hrj(f- z(b?c*uCJn=kuPDPmtsa^k;3n^Uf=w07NC?JHg$M<9-w`9RRjN8EFR=@HyE|$Is)&_X1A!Qm{*FygZ7nE+WKbMlzP0(kqN_{dEzi& z?i-jYV@N;ahnhk6(9j2a2BJ_`hMkwMst0V^6;A7A6NO~m(Vnutj~*F}SU(Il3NZm0 zV@YdcRher`eAWoz9XaX*l)f8U$~6 zK_+Y9GpRE0X}oW#Xw!lo-Br(&3_r6A$c~TjIA%K7J`Z+PSlfg?O7jH2jS{zrR|xU6 z$7%SeOi&?K)G8$)V!=#$dR8t>F+?t{vFDbS2+~MQvTH4z&ocR0hW~kQ9D#=I%A*;( z^S$V%FnI+;ZksLqV1!$c7jA2y@GmMNw_u$F>=d7ZnN|9v$%+ZJB7F!chOOEoG+P{4 zi8ofQRJG)FQJL{QCo=LcJmSb3x}0-?Y!WIAKg)8dn$$9IDk=}JH~-1@HG@?-f|rFT zRR}(*@B`%r@hVC5&r|N5m`9j&lb96YKRtI5ND{X&t*@OQSxbBlU!1P1=L_;>Wm8~f ztA4X&lOXtdzZm%+jSpV)+;~Am=s}qa6@1|i=noSpF|q@ zX(1s;Zp+R$ERT--j(Kk)RDsFuXqJE2S|-Jda0Bx*$-Yh0T8=P$>?8YW+8s_VK|~vH z|Lo=K8TZ3gCcy0xg%|*Wqs?U+gUor+w#5owdQNJEK#PyZ=x~?t{^&BV%&vr^x8oM| z6+aH!J3fA)z3$*CODf3YyJ3X)u2*l|k@4e+W9eShDv64(@!uU>n;??|uK6V{gO^PT zQld8@fzq+TG)zm2^!RYvx7U2B!D=dMN}m;$JpUaVEWP-#@UQa?8Gq7tpmftcrRRVn za*FCz&8e}F4PT;zf#fSm-#a=Lov076-(p77d1KXO2nFrE5+{c1k6lX0J_SdOm&!hE zwfX#{fU-92b9;6gZn8vKkAyYWhpaIZM75WjSK(Dt6kK?4 z9fopU`4x$aoslN@Fatj>%H{OlYjpDz;oLG7J5JKtiWhjt-QAZ*U~94RC10{AeOzXD7(>rpTMY)CbFs zO?dJjRPeh`X#c6IXsoJ5J9KBg<9z6CI;c=ZLP>17t)A}ngrfuX2*cmMJ??mtNbnMiRX3n%BtJHG>%*Q=>B{SbmllE#j#B@_~_O?d0*VP5So{o7jTszAks< zDHqNX&BxZW+eeVV6tu^ff6TeW{!=ze90I(LB;01-VYD#jMHZv zEg0@`8%g*_r}6=duBm?>;1fy&61o1U$NKBG6_kA;`OH<^@b!5YNWS1+z65tmot}pZ zQ((3Vz58HF*qFCvpykrY0z)hD&a*wowbRPYsNC&HYc8sZ%MZ8lGCB$OY2U~~8NU|1 zl;DFXv>-_UQV*%KcklX7PxGM;-4^qm!AoC)^Y;GkxY1dF{1E?0OI3MuWnQ7Yr#cGU z$_o+4x;~8=o^$`>-AwVE`$7x^XAlk5X1}UZ-coS+_@er2Tm~oGY zH~*E7kpzZC-gzgfS!rFLgnT(#4~Us=ndxH9maKh)om>tTCR3pX*EXF^}@P&4j%j>O< z2obYuhExTAHG(s~6K~j{+au@j4_+`s%nd-@BmAFMJTJ_1KQ_izS{VgAB<4F>9lAij z<(`A&zb<~=AR}BlPLm@&dR#U{N z!7!Ew6xzlVdQo!tKQ~__{D6QDmW%RE7K!t&gyup_q<`fL_D2pU{J-NR+q;!pT1fht zH>aN~V{sIQU*0v-_(R@+mf1_!t|4!qe?4nrrCQEM+CKP`$#M(79Gbg-${x`ni{g$q zbJpo~czpt=OZNqqixs|3zV{Z2gm}x#-!bAA;T#s$9*x5ggJ- zXmc-rc9isTbF()yMkN0CfZqW5F5cJHgVl|vjLApn7Mq7{|2{ow`s8tGa>o7WTkaL@ zu#xUkg5+G@-X+^HaPQGj!(BF>bcgV_`;X*>rwg$hE<^$G&QbsWte!y%g{KW!t_-L} z|4(xNac#IU2cs1+^RtuKjOHM`=%I*Y)c0<6z5MX7iRJfwf?5S#C$A!GA-{J~{pagN zA{)*SKsjKDW@Xj%5ML9oOx_h8UdR0x7kX%avozt>k7o>Q3IB~=rrkfkoXjspRKwp8 zCiL2ubNfy#ugMjtLSJVKP2U}ZY1a#H&wi0PuZ3tW+CG;|RRD?*=ms=1%Jt~K*f@P= zn_#GU3C$2j5>0rt{52tKcBs$eXp8W&Ncra0KSGcA(WO_>Kar|jx|dc12@=*9V~u8o zeQ%r;dxtWxOgP8hLQAn18-&L}Z(7ZCA3j0A&N*Koe#OhH7|t{>cF@_>!9FRh&~#Y1 zt*+52l?wCJbif}&`?oBsx5~QetC#Kj&C72Lc8>8x^C@|8mhbt>5jXT zeitd_gaavO9!orqRg!$8hiwVE&FjBfA*_0%b*ee4*>F z0=q44)tb9ALcQ301KaRkR9oY~==EX0zM+&TIrh#tqHia#u|7hK&D$s@g&R|( zT#XMwgtvLil!PX)xMcqE6@~Wy8-5>Vbt2`x`0G{}zXYRKvN{p>h#9xux$>uJWYf0n z)coq`xgEkU{wU;PwN72<$Lt;M687Jcheab4kz>1j|VZu3)66mLK76=DS*qcbx+*`mZTRlpEG* zf`Bo($j`$U{?o#8Am;D11s&qKCvU5kDz>EtD|$sc{y2S`rW&mM^KJAoQ8cxMDe`fs z6)d%p>aBghuO(yI-QRAp+SPCm1_HAP*_h0QVP?}L*e4LXSgozSqn+jyIj@`)K{U|} zZ9c@$Bhm;G*ap+(xYTwCp836yzt9Aear&0g#lK6;ziW73;8@CW7Xr0K%6}jg-ZmEh zN0WEUBMGd=?Nu*$ab00Wj9_%;AoT$;<5f&zbxyDlLUghI*!O8LFBqx%xjiwyX6P-W ze}&q_+EDj)Yam@6_qTl9qM4GH(PPd$G|$vE3E3MU?cX242eZBg8zq<6l^JPmb*r{P zyH-;;xfu}>sgwxK5r?w*Me$$YR{;HXSvr0Uz1IPyGv@u$$AGyVLmuZfM6q3P>KL~PMFYa%nyn(@7oeqs!CwfeB&MYhE^ONu?9^_QL*myAn|CM3OsxHoJo=oZwg@+O#O4Ru%2kA=g9ZM2 z^r|^?tf{X`uBh#gn}+k5m=XTo_L>`~ewF0%ng!aVM7@0>T^R;6Wjk2$oa1Ke0vv!7 zc=r5*?WuVi?E1&p@G0j{V}pR@=i`HU|Fu_s)=qnL(8+!${G_1BPd+6?n(}Vwr!kE{ zMjaRoe<%@;&+dmw=21Pf7nb4?1;xqAQS#Z6{ik+jkA9^&B}*Vz*~cM$eMI{cQv7Uj zCnLxvBzidEKFedu3~~_(*yMLtPY*eA@-(aR{YT0eBuvW*7_WE~Z54Qr*m7S|Xyr9P zZYz`SKsUiKGOj&ieO-9;kRUVQ%{Lfi2v*$vQQwFs;&iMF8NVVT?p~7&E1;+dPIre5 zj?l6%qsINiSq+C?$KF&V>fSkR5^kQF7SeJYZ`$LXUUdxEymQOE%g(Hs{#^Q;%l0uMqgO-@!^hvp1RsZCcMq>IeGGorxY=#5 zBXKkG`r2v4?GRGyx%0fX!ByxmI?uPPBqU>bkqTO&OfYO=q(c|yWriMunS#<&2{=`M}t&WXnw`OCo{M#oN-(=5>M$0E$kY+0}G0^uJ zewgRtV7DDN+h+bt2G3?{a37xptLB)0^bvliOLyj>P2N_6oxA#vANWKgmh?9EZ@>TS zhHbf9P0o>39~xy$FG_KLq5OlmK18go&R@XY-qCSoEAxby6I+}_=Fk{{81Z>2^9@n1 zxIuTSq0Z;0Yxg4A_z0Vro#IJJ0m8=7NiBF6qiBvVArU6)uZ^98;6wYY-$s-y7@!E@ zeA1P!Z04wbE+uc~LCg)JP);QJo?*TcJ7+!r3=Xp$OC2;!gEgG{PA-2FE+oc((Bh!g zrN4zZ9CUgHvpWHAzt{8S@*#?Sdxz<|W!)bkXDsH_c3N@Am)O`?>F*l7kC@tJ2?AzB zPeQJ(DkwtMuRF7-zM&6DmA(DyZ3RZ72y<5+6a9Y0Xbvoc_4!m)w!!b(J(m`_Tk$Db zZF12sk2m*scMkciw_Z?N0>2}`f*u}$`gi9DY!(gR_Ij6Wi8l*jYQgQUi_Aq8GSc}N z)fhIGb@43H9`X3R=}@!HRG~~Vf1I6)CiBT86@jch5U_^nt3gb}e{sIoO?dd{HaUBV zkT)^_!B1|0Tw|vCR_JMhYnMmE4*hJty0=+Eg=`Cy)*XR!p;{s7R)Jeb&PR62VD@hI zzDCRf%|738hTgKk=1R3i)Nge-j@?m$MA5-vZEMYyNF@EYR{RPX&?tqrVe9S4L~_w* z6rUnoL+sa8?9}2@XekEldG!!Bcv7F7)3KZO2f{>>dctppYnSJP$fI<(7Ki@*oHu^k z=F$Ht=^bT{d8}#$V*2L@*`F_Y2{{6C$R!e$lKWIeSsQ$gA)Eb*@YQqQYqN{E@%LkJ z3g**5QSs!fk_%4SJzR!B{mWcKZvYeXg^>D^y+n^GB=C|`(g9NhpW%5l{f#KNQrwt z=BWmz$vFXAR^03VC0BY&h1yy^b77Ok*`7cZynU3|2*>9kSaNzwkFSR&_8BdkY21sl zBrDCUyi~H4wII6oLD+C~4d&!I`I8rh(%JM%ipO8o#ty%>4&}~T-+Ly}owCgsedK5c zi}d2NGaM@W8~S%FwZ=G>Do`nN*R3`YKiypj0?=3t3O$P-_ornw8MlEGZgZQ$_%@?7 zZJN68fpOLOnDfoP^D4xuIKzlVjY)k`r9at{3kOXvI8R&~H|mc4g2Yt6@mi?E4#D1* zwi7KA- z3NN`}r#C61OsaG$>08RAaYZQPcOsi)cN?$qXNb)i%T9wOhWK2m$wA?q{`gDeJa+|J z+wMpWG3CNvaE0gsu!p(u*6Ld@)G}RXi`aPf?rzWhXTeiE+#_&#)1bWaXregEa`;T! zYqX=4+mR%-c4E1#*Bqa8A$kD%>Li%~;E%^K)>7Z5Nvu#8y=r~?Y+fDY>?nI53ky_lviU{8`YSt@HNdkO zgj+waLs=8H`1tx`uJCId!8)kT$wlxD$(mTcmcKtp)-UOSvm!zj5Q-jooX8+<%KFdi zzjW+9d$mNQP*m5?xII5RPRNrDnp5h zL<@`|!$J+^{?fhXPp^3uyrm>^ROQUaFdGPESTu8QLX;X}Joi^xmuHKw4{IrqM zf(%oq&*W)=52y1->d3K$ykTsZ+6Si5^~Fb?ZSfo4fCKn_TmiB7BNz>^ax2 z$8lIv15E!LZC$)=4Zpt{T$7tf>?izrl&t^tcygWK?eh}OY z*ef!kq2i<566X@Vf2vfpDbO%T(|E57yv1_vqy!tRfFHQ4@qU2|$vl{DMS7_i9yna& zZ`ILv^=SD6+HLY`CSlt{lNN3Hjh_q?!#1u3=*we7fUCbo`*U_`1=!gA`dZH7`-#X7 zM%?05En#}MxS42Bzi+@&O7Q54Meu=tdttERo=L`wcNZzRkR}f_3O50lp5JJe|uy7Dt4CB^J!qY>@9Eh&3q8peSE`z2-*Uz9xjtI7=62*n}_XR9u zzRFsCc|%NTR_INF!{|%eMg1^JB@~c%P9eRmzJDe-G7+#d$;y`B=f1eEqwde;N14eF zEL1*XBfVrRKP1D8@1q6HH76FgGZ2`Mz5b_%C(nwajX1aWX zrLORQTTwPqFAP&-(pZfM96qZ|2{|uSC*k6=MMZNlkQPD1gI<9$KAE;rx-faRd0{rKe_|>0ZarLd@>f4;^D7-a5+ue?w#>G zirYH7u?D5Lg80&`V`}}5_Mz*yPm?gIXcbM7cTHNRv_Fb?@^zpbX8hU^p`F8s4L+N4 zK9!`rqQ0<4VPHy678z&ua#QG|z6g@xehPjdsp@yORM|@Mhea?xJoNyN^$lf`8!NU{ zU1rIotNJd~ECKsiIc3>2lm~s(pMjUCl$Bm2zchF@yjk|kZ2;Hzr!WocBOPj{tge@9 zFGbu=FOmsB2P`hP9hj<`10SiJAbC_TSN%Q-sxs8ZL~&T8Embi&gn(|4#jW$yBNhix z;Scj8Tiyys#0jB7k=*$S8Hb}If52_bjSK|+kw=pXD<5Y`o{I0_-Mie$r+2F&)Fz%k zK7ISW(7tVI1m~Bneg~@F7-6gZ8{F?N%t%SNeRLcFn67IF+}|=xZ=}?dv2k4WV7miI z?vV89A6t&sh7fn~H$E91)1O>?6=b~1(@qz30Wy#bar=T|K=LU47B$c0J}`XVCjQi# zhfF^>cVjn=yC-?91Ak?vH%E(=7Ejbj2VgjQY5Z@wtYL``O_|QXODjt z`>6wTc{7{f-QRfoF3{0{!qz1k8*qg7WCY48Z}BCv^y>G?n=Y0epxLARqtIp-MJv04 z98)iLThtGFpbx}I9rs@E9Dq;OUX_GohUD!S-AE5j~pXX{}1c3b%Fh+%eWip(=cI?7U+#KsOg^fHqj z>rKM>>h5Flv`U8<2yHBS$qx04Oz^%WS&X1cZ&6gtAH;pu#`7zY##DMB z?TPNRUVjk7grk9xIPTGFW@LAV@XstMUR#3B>?d`dGWHYJYkl% z@0i^gLt|>ba7kUKT$2@eg-jZ;=I1v9G;n*j>rF&?XP()w#X_Mk5`y#k&iTK?z4`*? zEr{Ov)H2+U4tzcsyVAGHm&?d<+x77|`NmF-{QMw8P6szgJnI+@2m%_(lhW0U&7~L| zCcG`-!cCu~vvzFW!AqG~PTCd-I+K;txs}py{FmDyLIqzMF@e?K8xz{JAqY(pck8(k zUz8a~hvC<5rHsUj0D;lFpX0T*`VmFJ56$5<QfwsV@4_peQYW&San{j@`0l zyK#9P_&$PblW)P*{gt#zBS^kW<>~Ru^csK^P98iBQ?yXzJJzL9^=rkAA#DwwD*d;Y9eaM()UMd#N;bQKG%7ZoKz%ELvP(Or_+gKxD6smwoy*OJt3$^Y5#juc;b{QP)Hhx>ud%vHd9fC=2g zzBrfplmBFIql4tk%}A;Br9*;RFgf2&7_9r>hl0<8Vhl;)*BvGtbt}&CQL#ttp)F%o zwfCr0(ncdOLu=Dfzn;lmNCH}k7;1@Z1fqfw3!T5i(PV6T)+#)vFbnXtXS$#sK+xkq z*qrcDb!6+$Kq%R(caMAB9K9I6J&7WHfkTa7Jsye7x(c`jBg|emB*V_*LjC*nfQNVj zebcGS(O1kRLK=Fb)83PKzwSmBT6lg{?^@WcJ;Q96r}>bHjJKt-C~zcmU;zUE0TDP{$(6{?l5Xq<3yg& zMf$?(|9b(lI%F^LemoBXohoru<%FF#Nps2@@|C_*Z;r{a^A_&PBG+-w7tDS4njSc~ zul|#+qxc~7Y`jVATa&_Z*`p- z*ALxZ3t>J{oOwKamUksK(;ZcTdYyVVVtDq!ko3&ruI*x#tL(OK%#R}-Gq+q_TlXDF z0i9Q{`zA8+&Wh-%Jst6@t!(fQT6fKVJNTd6v}+Ug#AL$&^w(gkw}0yXFwetpROP>( z2Vh^|GjE;vC79>;C=*~7N7LtANZ$t7<3`&4nClMWZst;lClLt#z-t?R$GQ`fTGa99 zJE631=vSq;74ZyCa67GLjj1AXBhxJ)6M|Tup)R9{vvfzoJWtNQRj_)f`xEBTP_?noZ9eBa_e3!H@s4k3kd~;zFU0l9+hYc4siR75-Nd5E)Qt$VF9-yKNUes-njrO zL`&Qg;@ga%jstwq>$ceM;Fhf#^CVhpMER zIsH?f_^5AV3S`FeI6ogjWbocYK~bldUE3l~(cDDgVdnNAEHiX)6HkpP>Rfxfru5J~ zk)G1HkPG5b@Z+aUS<>nJ_{B-{jcfF4j`t6PkI6t!eJy+*0}aC>UM6DyZTE*(L%-s$ z{~rM8Ko`I1Fc$%y2KtF-(Eq#xetl;<3ipV3)wqUnOMcyf$G-PFA#Zb7cHGE2-lqeYV$6d~fy4ecfm&WjHb&u2#f%u0mgAN+}a5g?2-aeCjmc3eh^&-i3;twNarck&iFr)k0S}- z&G;1)EMFj~2KHwEFr+0-?_@t(u=m>g#Ey7?NnH_f6EN|UT_6$9>J0?r1!rl84uG^* zseum^bd1gMtXE&K6HM=Hh=;_JOgww-K^&{w$6tQs{sR7JsQ-Sc zK997d%h&4LgK=4wtw+5^dsLT?fy0aBu-%Q1#eS|Ueu%C#P%t&`=)bGdflUC70m9*; zacX&IKPT}ud_?ij$fI7M3D{$+eKHKB7S!m1c;=3~F$*MyWH4S;aCdM&P#;2I3&xLD z1myZ}OviS_)4#6(;dC>}U!}+(I6k*6SG)tBxZ$w&-yoM(uT28v;H(ewc$NE)#?HFK zJyNm01SGNR4ocAoFsY|=CPD&O3f(_#2Mt%xBd7OG(*Cz$LcGJBRDecr8pS(uOaKT9 zI?R22f!+?#JECy{@{B=`hE}K4Ucgpc?V(-|t3uZgF(aKYauJbka4&ra4R*gtoa6C< zpy~bPcviFb)tdI73TZoNK(O@&!Au`PCcnPmOX=!*+XFt=Zy2ohDy(o+1EeY7%V5gi zM6ZX{jfs5St@YYm08Is@78!TMUti2=+~>~a#PaGgcppr>&9e*=F!7vH2(6x-(oXCs z{z5Xc6;2J5^xptoz;gQlTWz%mn~n~mw`Q{W5}quE115p-_s+z|EKk!=<%tW(5qBDN zaLhcMj+6Bv51R!>OWbw2*)AaKJJ{P~UqE^PE&4er?hE$FfxF&1ZrHdt=!`VBBR6)w z9}m@MtU%KJt}^{EJKVPy@TDLS6{PBpyW3+88r%^OmOwIUF5(0@x%@RXm)wS9v_6>ASJtj*i21v4;kJodvB5nK{Ss zD76QDU_RT<_SN6v{y*ghlc(OqPmjkZS5OvXw|Hau zYj=1AF5uYhWgevqTAm+FUqCXyX8Ho}a^vR@1HVLxX;;CEDqw>_Bl%SL%va5F?f`am zs~2N7FeAYxiOBvN$ohCLla7lg!5;3P57^ixP&DvrJoEiGrbW9b?C;$`H_zvukP&Qo z@ZUWlwLoURzme|0fx=ImGEbnw*-E%?$o?}Z{bt;IH^{YSd(#`VYWwd;{J;hv3%m*X z%TV~9uD6o$K-`e%z8e&9q~4EP`M<*UD0_!{c=QwqvVf;KFpM_`E1kK;j#1WRJWvaG zpkSXZ5Q+pE!&!SvReK-xDvVPFKT3gzxk;DV6}`%*;I_XZd7Q5^QCX}XJ= z-nm1Eydm@#$LV*rpTN%i8W1I2E!Fh;tgz7S&)!Gt^GHkjdc6=o5UZb|yhZzOo45l< z7r~)+fst#FTo*8N5@f)|4=i8Ne;>_W=71k)&aOM+SG4zVnj{`}w<0dL*r&2Sq!{op z6pUw!eSNF*?jZQcD*hF7fG;2%&$bER0_H@!N0~ifM@l!U{=Z_EOhO4Q_xrunXZo2w z@Z_+u|9)P12`CJZ=rp5yZ_~g1B(Z-W*y;cJfBq}^%*bINiA?=ov)RR4fNkmt6uk5u zb{~f+80+;!+fUEiODoLUL~1;MfIB4;BrLh0w4T0G8e#e41x*K`5%PaUmlBXNIy9Ev zk*I05lQvYoaX{DB=Vc`SS9wk%uR?VNZI(`E=MwB?nIHn|`CU6eD+t0k zE#mXwa(i*^Zb?5c;?@D?$09JCjRnTzudsB7M&rMih$lg<*nDEq_BlyE<%7Z zJi$AB9{WF%K)g*m(mn9l;{#Z%_k`gu-GZLM{|UOy{kHaMN>Gg2e|Ky_gGD0=Tb$v$0RhJx%|rQ7Ut zOg(Ra$zAJ*a)9i4M?0L28sIBnpG^875h)Z2B;#4HzTA!&OxvF{fHU*&X#W5*@f?Wn zX*s5z_(o*UA)e>{TNt<1=KwPK^b4*v7}(lx0BqVTuFsPFFT%c3Jik!yRBc$&la#K8 zxnC*gkp?zxe?VSh_I`w;Tuj_`JGWcxm8{qNgqa1L_8t^WzLT9hTo`ZG1(etascY06 z&EVTbfwM1=ay-}c-*B|;iQd-!2$%a>{NHQv6Q(PU=f^%Xy>oo6-q0@)ne%n zFa>{{1$jB^^#|J%1iu~b)()h7%V4e9>I+dojHjpK(WWjTA#Ms{Adf}7?dH?;emcHy&`Er0pfA`7+kWZRzvJD%!+PW~sSuC5j>miyWPgWg zAjvrK-;m(!Wge+_v`h8*m3S~B(w@Xmm+KVsJPuXFWz8oS}R$-$+Yq5%m#&7sgpW1}jB$MO#Mbb-L5S|Iq8SHyu~yw$PNo`$J9gAxJe){dcI zTko&bi070bB7i1AN3_JM6-+X`6A+?J`hVH|Xn_PqI0yVpJge^R6KwgY_bY=DU$JbJwbyi(C-?98tLQPR~=RsSRKOutm0B?VL*cK|;96JrZ& zivV(HOFu?E^Bn2FKX3;=)PF1X!2JZ^NcK|m4`JWhXV?V>2AUI`aVwG5xaeta?bul# zwlN@#p89%~2v33VMqNzB`!~>o*Q8`+!LvF><>_<-L*f9j^+;r=BU3v}fLGG77UJOoHXL9^_f z2ys}F5mNO%ay_KhJ_0NgFB1|9N!!mMVdjcFfuwo!-nqG;xnZ$4m5^LNKvIhLXP(4W=(RU(ya5#+`n+?Y9Y`sWS=UfNwq4?LS(-V4UB<4%kqic>!nI zn_oW?=l&bi{vR-KWO&E=4W?d`f*Gd!0u=E>7s#9=fK-q6i(mM`@NfOjc8{H|u=f-8 z^6~$z}g0 zo>EVZyT*{2(-6;>VFyiarv=QyG{isoNcCmnVVTfs^o=O?CtW=T`!MfnCmUc(LE{SC z7jP=SUh(Bz;}Fkt|2;fm0B4wQ_#Ui)8NGkOwFvdPZkU=E&}d?`{IVMxCU$2#kRbs| zp-S(t1GirVkP9QT_RDWj);IDv6 zf8*!T{L0A4Wm`=1Be#L)7mY&r-u?LS0ovh?8!AM90l|6`Jlz$KCjVF_KnZyZhZPwS z@z2U5ELSvLm9z_c)QMJgu}Li-^16d-BOLCJ=gr)IV~0m(&ie-|qEiCKJ$&8~Z)>@Gtzd0qDt!Ui5i33eTYrYD)1wL;tu!4Zox-BC%%)) z@z&?B#j~;eO|*X6FiH(|W9&oIHv{f^4PT>#x~tx05NS_{)7{g7ntEGqj00#It$5$? z9Va#xyn^~%7SEX05+StiEz!1xlzi|25O$QE>NE|6OJ$WgQTF{&)CMhWY~KDG zMBA_9hv$9 zu@vw}e4#{zIr5F7L6~tCU@LBIbVQ@?GX;CEcuxCj`x(mNQcwi@ z#&+OnJF92=0x&xh&5k4QAH?Q(W~(pczvgI&sd0!~{LXg!4r{;*39KrfgKV?*v9hP8 z)AfcCtN`}?M15|J)?rrv2g2WH553>O^O|wNKQ0eyWa5up@%unLpXk3!?1lYOJF%RB z0$A2ExLxdqslD%;`h|#$74;#EWQ8c{zX5B%eX}4SQ61K)O$Cuq6T#CfAo=YwEeOo@ z-_8j6!C(PNuaS5-TOR23H^%cCt;IXwi5m`U|NVnhuipv?tmmvfl5#k+E9$fL^!m5M z{Y^yb!+Kh%9@^qbDR?_B5HELXd>p$Fsow)TJ1$VB@|`xJSpl%Pk<-xsmQ2{U_zv7o zyO7@L&R8=4^#DgUG8np}XpLXIBYlH?1ol!pm`=eQ$Jzwig!Cviv?I-4For_h9(#y8 zltvnT{|Q{p|9`M4^Cj6~vUmT{dV}iSF$03FJJJuJI={ZnzM5W#8vvMYgndc-lvcP| z7){WBSbjaxf6Ll+)@Nfv>JAQg`T(2?oceM2_)6O4vT3J+|Ifw)ri1+7({=lFcc0)~ zo}^FepPVbAP%Q!B2I_bGD9^a0!ZO`Igt#T`c(aGu{nfL7L_9EK$NVn*S5^vd1PmVw z?DT%o{WzjOf#dmAi4b(%BlZNZ)_;#xoZpah1jY5?ZLI8a{dVOOyh8knH}DMtwDxIx z{C2pH4vY&1WTN|nm3}*+O4vMo#q#cv>o-aYL~+aE+F}w2xZTon?vKZ&i*vx;bg%*n zTN8Dt!~QR+zUK*ytY1Hn?C*?Ro}oXD@&{AX=;MfRn3EPbdx ztMKaGyPR@7wofH$v={UstKD7w_Q1j+aMItjT+UqLz1BaB;2g3tKNVtP_Y%C-Vh`g8 zs1u)MgRn;;Pi2+HD6+SB}8Q(|-eh#(>?4h+U)pG@g@Lu6xX{{Js+UZ~Y1074PqG$FS5F zuHI}K0Ve;Bbco!_%T9MMhk=N!2$)t{PnzKs~WA@=GzDDSxK1a7R4*pGsmH$d3mwg-Cl6KYgxC z%=dsrT~h)=9Q^FktrtQ;6bB0Fq20hc&Wb5~-I+y=VC0Wa+kL|`^iY`2dwT-ou7=bQem2=;!nK2Hju zbQvwMZ@CrW?!FyW4{Xr^brv%~Fk~6UzM63~mgDpB&Gz5Y*ml}yJUSu}W$h9W#4N`UJgWdfo9(z4GY% z4)+%dG$7VcArqBcFm`gBssOS$8av$m3ghYf?USxI;DBgCuiaela32`T{2~}W*63~J zJ2Sx%Tuu64&}|8biDz?s0}6JiYhxt+dL-djIb)&~uybDbI}>AwL^ zhwFRn;r_Ys8i>Um0(X2~8Bfnb^b?~1p+0l;_=WBs?^lD3@oYQQS)Uu>@Nj{+F@1T9 z_1{PAb27hffZpE`7m(Y~Z};RYaxx79rAzB{otXXu_`Bdv7KFa+pov%bsK;*Va2(TX zNTefk!Nxmi3fZrhAap~bRw4;*=`3Qj)~^21{Oh^H9=<^o4LDsx5qDbb7v!!4#|w5k z1I?rIgGhd7WO4^hJ7Aw3*vTZDpfEkyXE>qaU4su+i?H8b!x*jC3%c~>86@LbufD-Y zHi;iD{5vC!^?BD0nhYxQt3kBl>`MONS$3?aH*jpsu#!T!;}gqmF8~I0vF)pC0mp)o zdQCP&nBm_SFNX9V*F!#&%bDQ@-N*U|i$K`1-i~KP_Ti5Tdsw+Gv6q#R(8LFzqzmo_ zX=^hb^Co_KfwsF{l_%mL?1V`O_V3sL&IHBv->rFevX6|$<-On^Z~4iBnI{x>7$yGG z2da37IxzxcPyY>UDE0c40yeBop*)_MHPCYA1%B~u zw-3R7c>=Zy8a+=FsQdy5V5Qv;7;@=IVxL+46DUvEZ4Q8BL`*wyF6$0!&_AY8xGUlv zu~#6SiF1{q>xZ;<>wTebe*HV%{X48jo|8)O=F_hQw6Xk6^m^Jb`fo@q_MzU*?yA>7 zD)st{aOPL8A2-z{|8leYUgL`J*^@ZCR_Ma@n&ap|2G-)6Ib1qDY8UQ&_&`^052!C! zpKIfp851a%PH=bDJ{p5t!Nqgq1=`tvqbtdwc=EFUtbRJX=wQ^pR@1IOv7~&IK5zcaFC>euEqih;RW{9siVgP6<2>liCp;DtUv57WQwu7~Rb(U8yW# zhxt-GuZNQb;>y`$tM|Phzzjng?TBaN4v&WhDsY2g;M8OvP230$*N|mxt$=`lm~M@+ z$4Yw}r0KkCwl84a;b9rG#wy0)cqZNV8mD(Q!0i|xIo4u_7S@)H0Z)CF>NWTM63-P0%bBO~GQZQ=3jhgwr1}Rh z%mF{pxeQ_-FN4GiJKX&WnC>6jf0v>#c{vrx{d@{e_cyBI{2f5rC&GZTPALT2lV6$R z`D$0$4I({4{=PN*p2{reg)z2V;dhdMa{1&1XQKbscUb>$dP9fBFgxuL5cPJsKGSMB zzDMGfJBWrEEWeNIcepbJxw1jB7G#;h48v$V*)nKO?<{D_^~_D>fTl%x%nL*+dVz#@ zR3nBmzW|1hHF}$UUM^@lkVY`WnRqtb0OH=sj_L3SY_!!L?)38oL=;e{Vao;OsR3%} zhQP(_NSEB_ONgj1xC~8X*t0%C;M=hd_{YDaeVp?U{1?RhKTzPv)H~T*Ypx4Sk%IXJ zFy&9Q&w5(O9T)m)gS{9AIfgA4iu>rWh9TPZTy`gL8HavDj8`D#XJ$69l7X%w<;=n%IGob*tJ#O|c(jNNq z=!1^GKa3*(iMyE`G3xInrtg*m1@M6YIhkKKKu@p41!KyN!q{4Gt$X@$fI(rAgpJSF za5Bb~@@Kr@UfMzkv>*T@P}bfWlI;dxFr@y1>o-QZ(h>wm*Bum2fI7JCL-Z3RJC@!m z+^j$YosQqSfLrVna3_|V>xFW@y?70fyraEUyF)#&zFXVsPiFVM z)yVyRg5!n_bg191>%RfILHEDI-BWM$!%GW_t-ydG-~_=yCx0+ml2uDCKYXmw+w7C% zUq_LITtjBs;Q?QR|1b*K$Gv@wxcdpP>mZFdFKAZB^SmQI@Cua(`(H*;#^n`(001BW zNkl0L8jZp|B3?B%=GW`~Cj+_Aaqno)?0DBNXpg)VTge&1o@5wEC4j#Oi@3z$#?$BVillR9#S78MKK+XqG$|D4*OKa{*y#`b z?k{TV10jL*e<)-xbSf7#s|#|8eWa1dpJ96($z4bzt}FZ1@yv7Q8oO){uK)gIxd`}A zAJ1hy-m-w5y_vqu`kxO}mhHlKyAW`|vpyu-_vycXmSJv4;J&&dp0~tPdA&Ofs{Cp6 zbegaMs2hF{4`>nbR6UQ}{r9PZ!*E_a0|pX52CkJY;UMZ-BepKtaH?G&17A zJclKi$C4c$w?qn^$0D6U-=?v@l#ktvz8t?|f?ZFy+@G-i(D!l|LWY4Y*bvX^4iBIL zHx!5wU{Y7ay}t}kJQMM4dWBAMWceQ13B|)`$d|A=p2>PNeXn0|e(xRaFF@1t=J-xm zPc^N=9|K)pj~(?HE6}6&sQyp77{RZ%A1r6UGny_`0;qUC06|Ia>#uEd{}7SQ6z~Im zDxQ~DXbJo?@{yKUo}Ly`MlR zo;!}`pIvr718n(y^&A~cH{DBjW&D)!wiAt;oqR3llJK?>E1^$*!LP~lz>Wi&;VAtyuk&w+G-E=e$^L|PLH{Gj$Lquqlt#r zQXV96PUEe3zQ{jKr%eqn(Xi&<+5RV}c*9^9y!w9Kfo5?1o^%1(d&6M0kK(Y>4q2RF zA`Q@&QN(|yUePldXSUYkYuGvGsL$ICn9Q%~e!;O^zdB!D!}Y;ALFw-&D7;Q~xakNu zy*X!})@=P=G(Uwxp&*{!{Wm}tkmJtCEt${6{@caf2VhFzKaF9UnV>u3nC`!Q?5!8b z6nAQaIKNfKHBE4zz#yGpvp(jFki}k^{u{;%W}EwMnD?eravD)!k2j(B-^2xEY!Bx+ z#{DI})5nm%BMI82Am#gNC$p5pVeS5}|NFlj9$(T@qy#t}5ETX6Um%>A?3Qjh=Fuep zq7N8ImDa%ibX^?A9`N2B6~o_=XeR{5Q9z%4?*f5n)eeoIvDj(u0yv(Z_Qo^!&UP@s z|EPDie-UiK7-$e=XlUVpnw{z}+u8uD>f`iHxSegn9Z6I`aXhc?NnBvQ96Qvt(R2kI zzSipX2jJCzlKt{(N&MhhcQjSkYd}GUrShwET&4REb76Kua~jnb(_V3XrV^()o`GRP zW1GT!k^W+|L5ZP>*JOLJ_Ngo(miqi!|BZR&*-DeBP-~bmPvF9CU*BugUQskK7H5#Ey81Vb2w(FX2Qy&upG(G#@w{C+!5(V=eI!>Y8gSidsg7s9 z-^R40f011WH?JV#kN`Siw|+%EJj3o~vj;U2+?XxX^+Q}Rs>{AXWjC03{(Y|Y+t&WO z(;I-cE1<@0--E%AZtIcp;C})Y+iUDZV*NLeLcm7sAst&4*G_S#3+_t+XPMg67*QEN zbBp}|)OuD>AOLDT3^|b^f$0J=8sU7lfnp^n$0D61P7}l;q#&i~h~6cTdF1RHo-i;1 za0TG)LFVH|&^d1M_IW)YCGG2sXWu*9F}=Y)83*_R%Hnyn$4Xc3fS>3gASd>Sae1Ze zM2Cu3Ju3al;A?uc8XI63i621z9oB#s0)|ou_5GoEPFG7cy*?`}bo;aS(fSNDr`PKI zy4$@B{Fd*qCK=FZr9M04S(e`kW&TB45C@6pyP9Pk6ZD{ke2N^3xIKehk% zj?($Pz&*A*(d%LCs9tcFnG0y_<$y>RaH3$V*YCG*EJ$FvvyMH;a!38Z6(_fS4D6eB zFnJ1uwW+ck$2ycCV4`7iB~*h(EbOxSHzusVg%0KC?fEI4f#eY>oO~zb{WWfwPJ2(0 zdb04$VU7KJZaF}vCk8NWY@nj=WH+hLs(*()W{x%^8PA+MKJ>-BlO5B+hB%T?0Y?(1 zf{J){$WWhMkv)kYJoy{G2Q#cUk5>CncE0WPVRL=P^JmO&5haO<+e={6lBRIFYB}+0 zAlUnOJR4ZmxDS+zaQ3;1?Uhw03C_NlE*jAlne;1d66#2$KjuX7|4bAo-34Xtu)>%V zT>oigffVKOuo2YSQU3cf({H+YM|vhw?C)rYwTuaLIQ&(6P^k7N#!m6&l;2Op4Ir?C z`UEevA)t#D&Y%Dvd>7m?A8UOn*x^xJP;LeheNQMD=m3ySKs|-H$^;LVk5znx0_(UR}+to}v zphNW}~D8P5(z0U^OVmjl$g=CtV&ndzi9|;$LZ`eZZ)}pCY=h(P>m~IDlRoXOC;O3(UiW zKfsgyWit`8a3=&3!I3at7{v6?UcbJ2-mvBDzn9-tFniz$RAcaxfJZ7!b{qwgGmOc-v{i%lDe=D7^ATrXBZEo+*mhV0WxD{K z@E%_L>iTfFj_~l=SG<7`9%uUk)*T*g)pIamzNY?+{}`Coqchn63p+gcdJQ;VpBG39 zqK^AaoX!}_udMz@;F*4@K2w5-0t(YxvD1bQwRrpQSIc|Gyj4s`^4(?V_niG7;D~*& zK+gJHK8pEU=YO(?hkJuvO8PqsD*8P$itX#E&v*a*2wu@|m)Pfe&hg=`0*)L$q9AtO zp`fHRZm{p*juU+}01dFN6Ls7aW43s%dq+D=i}Uvf5J_(u681@F+gtDL?yr|8P?3FM zEB@2#?UOEW=@9gfHxHvi z%@RK#63+=~IBx$El4F8bplIm7_r~+~M?NF$ehJ&+IktfNf-~%pY3-qn?0?Z5d3$W& zhhUFp*M6bcvxEJ?CJRbaysiHvTo=T88|(p}CwIu;QA~&gJuvA0{ksw7Wxf>8{{W!p=z-YOD8<-cN%uO_!!XtiehdTz9 zts;585b5$`C=g2l|Di^lq5?X-0!0%D1lV8kIO#C$FWAF~=DEdd#p2nk^~?a-k&Fx` z8zytW=^lG0AVhogZ|3GYke|2bBe#e9M&?50M8nZb16AuiDfOIM@R&nknjUxTP{%ZY zRA0m$@%bjK$#IJ*s{H7hr{kwDeq__KV+PY-g2$hzbCb)JgRRfQ2m+aJ_&%PJt;ah5 z!t~s66{wtNYkk=4L7DvOl_#TfYrZ4gKh=LHShfpBHLMPSI71cea9uQ@D1_|4ja=O0 z6$_^=_Tm;!PpB1o2^bqF}7YksHT1dXwMj zo>0(lTraq5d@}P{Pv2fpmI(|A`Aa1*8!*U5Z`x-nDBB8%Rx&LCXG_3*gAM!-0c?Cz>7Nr;ON*ZbO5h} z&-VN(tZN3J3@;^~kG``#qMPmUuo+6YaXUP&*4W=4^Qbh`?P}yBo&-&eonx51;>R%j zB@BF7UU{D8KP^9UAngjmmi`-HO?Z4hV#N#UpVNkCdUIdV2uE`b6W8R zAWwp^*5|@{T>_4WKz`NwhuR(Pl!xmcawdZ^@Rgfb!QLyLTY^Bx5j9x70VDk%%e`J7 z{oIEtHCj+)$Y9^{5i>Z@NWo-*u-XGg#x7gZMMfP*pxE1_a3-GF1`1{i+)yA!fJuEj z>~Y*?9?1E7&pX?>hImdd6OHw4*1wz(x0}YoXMzd6`sx>)wO1moJ8lq->w0a5%t4FS z*Ym3acRo5(pD&Yrhl1N0t=md>Ibl14dc6I{QGBJoMFmIho$9}hBVdcYJn_sHM8hb( zBX+s7217t%G`)Dkv}go`?BiXq9C`&887k?&0X6-$)jp)XguK%^b&wdo*kU(fSscIQ z75;&)#-ga_3-$L^B;C9TZT(#dgz+hD2fRCiEdBR=ct`&90Rv?ch8Ko7cV<_^4LY#H zK!d&U?;~yGcDO7XZAXE93cKIo9%_&}{ke}FrQjtCTJD1J21M$&e%d)8$2ZO}j2f4^-Y#JJjTsFQJ2j4}21 zk)hv?zN6hJW)7Nrk4VIGMXI(Sa)G#hNBStWFMpfU$yZ@4e-qu5Jmwl71@+bncDQ>K zzM%pi+D~Yd^6zNhpUP%kSP!%| z1kP#U;EX?Am`v_KH;gb7-m$(0EJJ?@75UX65?(G3G~<>H9N`KW7ssgoQg^DX*KgE^ z3S{CTE05vatF`H^94fnpaM1d{D)DUK;@Kh4S30JPuunk$NBcX2Ou2aGJQjuu`w1he z$0z#l(sWm~;AO4D29Cvv0`*yl=WKsng{wIn1MuZ~hxrORu(l&z9ebqz2L67pNcwl_ zHx4Z~U#M1oUk!1)yb_1CV8)2$@o*T1a+te- zqu-!_Z|)yF&U)S1hE%D~|3G_?_KBZ9k5|N9QNF$qZE>tbeJ)3&{1i_YO(zq5G3;6R zzA#Z&pTiC=#d9LRZkLY8pKyTKe`6*n#c6{cPIWxH!yV&*LEH*56zud4>k`1wC4mJK{q-JOD=$ zmJDm$9?P#zmh=wz1e#C@p||$m z?FIXo<9W2lq4>QF8bH%+>%aZn+mKn%3$p!4=IhW63E+p`(DAB$BKp6c`Sr=2&?jA& z(BhekZ(_8)!ZPldynP2_%edz2SwazZvF z3nertI9MRu-1h*NT~O!C2~~%pAxp!GKLb-qrp=dywsiMz5!(M!;WuD)pIT*jwnt?J~d3wq|R(Yg)dX6h^I-aZ#rgNPd2wgD#9Ur|RWalnqkBME1n}1M!usdLQ0&(Patp8!Q zzsVkyJB{b}w#@gcksR^d5v`(I4!6Y*OE3D4_uuXfIu&Ni=GWfJ;hB=VLH(p#;sSE} z_1hC|A3+hJ9qL;CuBdk#Y+^>F9O|e2jTiBG$H%R|r^X}n9qy5Q4=7N)ns5y4xC;c= zZ+t<5&<%+n+!ezQRXSh`)6%GDMP+Cwut1FvaB(O`lCDWwkWe}HJ!27ft*Pwks3;Jj z1v3n272p}=cShFipaGPs=a@av+7)v+64r@#wga}<%c2hz1%;klD&s$wrTXR{+0@?| z>8Q{7hNBkQcy@||?UTV@xUN5VAf9L1P@l^)tRaFu<%T;ygKH%*JMQ`3st*+q`Na)V zaXeE~8QsTN&QksNE9SBY1e}${Hy|J}7f-)GPWIpbjDAnA;NpVXom*G`{pi27nSQA{ z@an3t2l`H~%TZ4n>={Tp*f*b}1b=n>~awC&`Nf%BJOI%IdKSB@R;UnJ*=*ZkiW-k@C*1{>|hz%!+qaupNPAE2}=TdH=fXo2cX3r@=?PT*X!dz z{Bh-PqSw=g(SJh%CiXt^*;NMf_4zoQ`L(S7R_)5UFX-LKLAYKY!sdA1(VbvB!Uqd` z6tC_AmaEV1(Q?6~Fu#^dC%8N7{u+Z@fi!UES44{qY}W38RUv8nV0Im=7r0j*IrTMO zgPsMLI3?=y(vMU9wKl@py*29hmnd#EoNDO~hS)>Z^56{6`y z)oBdwX)WcCD~IG*w*kmipF#R)ecogbH6hB@S`7FrNIoerZY%RfN0k<^^pqoV^sNt@4Eis9j^`Emr=(S zaPiDA>yuhbVU1&{9A^U`)5h*kh&l?1)b^#Oi5#iaF~~9zRO> zgYLf01{i+mV4B$B!C%1voUhLdtZ{rSzdkUzi7&mj@s%uK?|pUIU|-5XsqZuTFSMD; zhCv+(Z3W>dV37vaD*+``k&U<;a;g5?C|!xY)PxO;4>Vb?H;%WE28kK>66s>D9o(w? zyA~{g;tJu)uN5hW*YhjQj>sM=zqr4q-`W1qFjMFTLUl&PUpwR3^bU6i zJH(2_jOhl|%Hfs_;3^2p^~|mAB1#WTrTuY-L=FkZj?*j8@Ucdl$G*Wk4<^tRibg^W zsute>+~5@0M;nPW^=3hHBP=kUAU0vTp@khY1AC_Hkzca|;C26*?a4pV$-BoI#@-$4 ze-L-18zpfclqokKfIq$Zf1tpT@rD*K2IN2s>4d!gF`WK2dxXm0dRkPw1U~Jo&&%QEJ?vNuw%TumP~p*p$Cxr|QUF6hxa|XMVme$P z3=HlBE6_kw89D0*SR=hPi$)gvSoE*hqdfdQCv7hf5_PB`_2X4+wtQ|6@}){ic`vRrg3HNqg$^?s$lx zGM@H8t z@pR1&NTUS;5};~FdRg@UPV9&tM^FDMf>e5OgnRwx!X&+c2h)`!F!uD{z%Pz>gn|I% zrVe9wJTuGLm*;*zK^QAf42&~=sXoUQvM#T&FwAe-;l5WLU4UdaLaM)dMM3ipV3)kxNdw{fS})>>!?6>OEx>+aT?vkgcirh?!VFz&%S?7{WlCUa~|Q6$pp6!N!u z0e+k=XkY6E?lFGNJFQjmE?(Sf`()f(0mZ66)MfkL_V_&tIB(ce^lwVP567krK-pZj z(H;|a#rcmfcrueqw?z2d`rfqx$W@;)U9JBSJ9BPEeB)Q8u#KJ4fIq|J3$`oY|>3(&Bk2m=oPvi|xSiHdwGc(GVYthoS<3A%uyG zf97+}T_0M%;H=*en(1kWhpAozjwCD@UjKQ4T}%1pNBCo)(-}0HVF`s&4l5y||Aful z>EBZ3lzDi5EjcMtu$=Y!W@mFe2c}B*--Z#eC3~42Y@pwYXG(a*{lp72L3<>!kB$rj zA#e%|GK3XQjZ6Oxz#DpwSBIGR7AVK9nYJj>srco7KNHtCxc(c{nI}*sdr|JSHy*~t zfq?|Pj+$Pscn3Vuf8%$z-%#td50>u3jpO;1UOvpuoIVFTz;VQm_}@42S6UW!B-#9` z{usy4T^muzu;6m16I1c!Mz&$`%JxG^+lx9V=kW44X$Cw%wnG(o1ZXG zdBUz{PkZE(iH~XY?O=x)Ej!j@G6^4i+#VXZ9W&AeWbX}w)jq+DO1;xtXn@!@@XKJz z-$Vn+v?JM(W(dAiX3**}b-bfKZ$*J`T&=M?&8zKG;?B#2LVaG|T~I9nAyILD6{Oo^ zAC!-_T_7``=hT3|KnMcq0&?6Lxz$I(9?9!rc4X3+SwMoeO(2f83(i0h7mzP{c8a~w z69uHgjIh|HI3Ib@2aR15LpBDN3&x?bvK|R%SSEYf9t*s&ZHZ@e0ipC2IeG;X=;n35 z{rU$)bF8Ht4r|o+-;Y9PS{N_P;E;juceuY~kY%~xW24@NW_2YFF!5Vu!jmnl5m_*IK>)0IK}S zC>Bm98T^GS+~@)IK6X%_C@9aUfq;|ptM&W{nDf%r&(LoGT>bd;MPYqb>a}Y_#{pce z$?w=V=wIv00VVx|7wiQ{OcU|EJ-%)1liW-0rPyO1w-$jMjJO&YjXo34V*lN1xB!Ig z6H!kF#`7PRg7r=+7(+ah{kM8S8jg-V0X)jMNT=w3e9(N^fAAKlf>9CA#CPR8*6}#> z-wzG^8VlMOPaEKxzC6F$mWwZseos02Z@h!ue_d=tK%LC5#JQ#KaAy~t1O&z71Bmg< zKbVRXR3V*9dP@TGI-Gb+KIEq{a{(lPF!$YfoZ#*g{Z&{lX!hRPe^Wfsb8sV zR}L{xc{w~JwM2TgD$;wdw3B|SYhpln;T^+YrOkK)?*lG`oT@}ypfum@5Dw^v9A~EP z^(;4&Bkf#GygdOBC{ZTI==T8}{MB`RlcWhDJl!8trfJ7It#&Xy+Wz@y** zZB|Dp`~PR^T0@*t>F9tQg@Ta$(r~!PmCS3dz_+ZWG}G^;5Q@_kO0PTp2=6p0iF54u zy(nQLq^bBIQhxN-{i|4m{pmxGC$_l>@&FZwsTnr3wojPYZFFAJDaWT+C^qHnZE&~cE!-thW~@ifKJY%1UhI~Vq1YLjbJ1}U)psnn z;!=OqOZLuiBRPcK`t(2FW6RrO9&ed+XsrT7F1~P-+*)FDs7(D}n%Hrf!7nXrh@kkb zTZYSKk_oVXJg2?l;$&xr)g{?A+gnCpN`0CmQIER@z_G*?`C77ez)Rkyv7i<8t1RC8 zfOKT@h~bl{K2L#dTT`{-jP6jZD8akTNa)YHp?<&|rJas3&pmyZo$&N^Q=-xuK~Skw zK<9u_r&ARd+5k?ub{IW$kX>L5m0;y>{9Kf8A` zl0|ijI^J*g+1%`xd$!v(kbXxrz3?7C%w32`v)h1~%Js|rOvo0*`niE^BA>XcI4tT; z=L)LI2|s#_4!z_}Z%183 z9DbIq652SGsa^b`Sm&6lVtszG;uo0^Z3R57^u8|sQ*Q&TIpc6HZVnPYME6(!D*2m& z*^qO|JO&>=r!g>tP?Q|^eN8o@2UkS`MjqNFW}FzViyKBqV2)Hec8^UsE=X)Rz~He< zsMEE}sEu7oQs@(Er*?n-FE<=88CI(G==FO9-9KQpQsc(8i9vRC!G4NG)zJHe90Jbt1CY8Vw-B^wKOjRg^by*^n-0@wjS;9??o^=5eXIQ^=0 zMdMNU)cJZK>M)m4$%mK8`|q~#GyIaR+S)TMr+uV@fHI6-y=jV3UM#}o++r63_JaO(d4CutMsS+BudP1EdJrH z{lU~}oa5<8@KDpG`L9LF*tPT615%%n^NZUlAPO3Clb(BXB`zxSII*Ajb)l^Wl30}T zUduD0ePtoiGQh`p0D-&fQ(K$6oalFzH9`uw2IG!$lv@W|klk2Z*--|gU=%?rKIjQT zGj*(X8G&>w%w|Eu;5gEyG0{(ooo$9ouKn3}7Uavlr(J}z9cUi6R(`vKzKXXtGn?rs zADmLs?|0O@r|$;$XhXtAfnrb30(a84%&0{7Dc>~-$|wujp}f_`?XR;U6K~bx=*oT% zCmHh;&kpIwUMmdT6BJY)zg%DTY96(@WyF`vNX``PikxObm_IpQ2TeLIS=de@&0Y}9)PVot)eLs5dO#annJI*hz~ zbx7npI8XGTkF;N}ONWJu-$Gw~<{dv3MG!TM0!jy-zGvC5<8X+!QYF}gNlXu0NkK+V zS1=V$GK42d)LybNmB5V7L0weEON9bS}Y$dL(T7oalqNt?hN2(q&qw z_v7O9#%dZ_wsWu3$N^7MA$TO}oR|&=bvzay;UB&0)CST?pJv}th1QJ9f>{A@ohcbM zJ~IEHTFr9;&SFjGNl35}A2+gpqkvggQMib^(o{jL3~kWR`sTB!%2LM|fh;;Ef$lPl zC7okSd;SFT{PM+NAo-1R%`E_}UrLmRgoI)k?Fy}S^~$+6uI!J;Asf7<&s~!EDfq&Q z0=%=AOCx;4t5M!jC;ht!p86}5M)Z1ibg>bntac`K)$m6e^2T1;;H3b5s;B9q1l~5& zbT1nW)3hEPAWR{xcp44lA6uTtC12t|9k0(ltkQzRRVaMumUKGMIG?WZdkl~ggEX{{{X4eMKP65s=^LTT}83vR2%E3n_t<`*1&dJP-Y*G++>OGc~ zeI&4$NkR|$j3P#u9j#^<_t%0utjtBMgCet)7a5*OuUdLZpWo%z<^01%{!mDLZrSGq3OWS07B2rBD3XnCb5@-kkyF6kNsKl%Swlzj8 zP5Z>WnhL_GU12-Cx|OJbx0Y{jbkP^&cmm)%+MbZn&~7w=cz`B62%q(m|F7~aMX+)7 z8)rNX5OJ*ilSt0249v0%RHnZ9a+FS|yE6`XQD)LTHa)9#p8%)?w&ca}$19BDdX9op z^jm}3bpbp0Re0CdgT3E6HAlOhr%!eKy_kI%_U~s=ow)2ImzuE;Ow4tDo)gGOOqCvh z*pBUCpSMVXMonQtARms-GkTwI+e>}&;W@^AT=m|ZE?B-^mUZ^Y4ohW{h@AZje*0!tJPBYbuU^#EA_j$tE#ejW$BnKx`9;!gbx>;+)W- zr)1@6a^G5J{X7L~LcSF{c3d6gX;A_R%#dk;2QN z?m4<@yDM@m#ay%fpNfn7f5vgJmLFh!ne7z*x|wM?M7h{IKN4pab{zt0#9f!nu_@)I zv!DkTz{*Vkj;kv-k+J(K@8pA)7Om^B3AdN!fBk01grYNzqL~w7wMp=&TW5Rf9k-ko zAipwSWUqd_>2wki-RAsqyJj*2#C?Z4)(ouY`lvc_x1C?JoncLqO}T+#S0$FH9v=w> zlO0qwMjbyh>ZIM{Na79clC#Mpw{Sp{)4E=EGFq#|(VBFVHB(!)ejYGB>8oq)9%O?N zBEOAQ?(kq^$G@ey#=ZdF!_nQfnrai);z&Ptc`f_IpHP&WC5c}gu(x!Rc!0K+=XKuW z&43qEHFmKIBwrEBqYBZ(axV;PsQndjUx%(|JphiLG#ezWiE&zE`61I~nop1Gd8(9< z^~w@dp)q!C_cO>E=fto||EfiTz!u!}f=mCs^b5A9B3qL_`-Bc*_9cyj(iC!7;%h{= zG}}2O!PL!;osPTuu>*A-eNF7zsaW;Z6aC*3Of0j)wQnaj9A;fPwl_`J#U`lV8D`V8 zN4AsD@SgpsOi%882eVi2h>j(*N(z!N-|ZI;eqwFk=A&pN|GCk`UyYs3Ro=cF*U?s` z`qsOYOb#!h8GH$hDxetBgeZ>0HHz3v$G>0Z>NYra(3k=&JiXm|xW$J}^}XMWd6OeQ zB}Vl_&Ig5hFU+Gr0-Fq3B96Dc40Q)DRPbX=jq6*%CN&k=VVaAgM$&8lFC#P z+H#qf3Pdun7-za*x5j*ApZ%!R4t@$~(b&rRcyrja037w=oTMQfpBi}JPA~O_#~3*@RFBA!F(8Q#;U{X%E1VbL+eMt2BLgXsF!I!S*@-(IT zbb$KKXl)j9h8paKpc6-e%)5~qpn>^bQ{7oy-4qZG)b67Fw3W^u5c)nX2NI@NHw%EE zouhd2q*|?}@EFMj-Un3L3KZAQS-0KZIum2MzIS)tZ5f#voO(e7;q2+3i~_NpLVimw zs?z=_I5rB!g-ef<>U2KVs|Zfi-eEhAN{*b+Up;phd33aOp*w=V?z`Q^g}@RlWe^xD zy&~nf;uY9@BnUjOfhQ$;3(JXmTz+veyb}vtkBKLIX0=;CN5tudjFBnN?JdkZef;x? z@5s_KFlC8fBKld32#Y!lA$$n6bIgcPry6oxoqZDbj~gcBxm2!fh9B_0ey9lgqb9Vf z!TZ@Fr7q7$jOi^!s#t;PBzym|#i$%DgqDKy2USqz`44QtzU#X%q2*^w3^1NoNR5-tTm{_ke2;jA}765Pl^Out`;ZvLMf%3j4;(hVx zjaS^aX6{b7u!fSiiG+(j-Rztq3}F19j=|}AoL3B|qpIlFg>DWe=qtzoD)qoak)eZr zl)Y*+KzFrl71bAw!CQGz>`I*D4miNA+l%e9*SWRM-H1WY%>*Tn>~P5g8$Dtdpi8F7 z$h$@+tArH8pq#j>y9=xJB=+WqAjMMO?B zShlnnrY-tSA~3PD|E>yWQ&+`Sy_0iJE2^ukr?yOWo9!qyB9bWIvF%>jyw(}{Nn81= z5BqSQvJ3l-$-bo1TCjZJ!?t2O%bY`Jd|jCP9R+hlVrN7m8MAi;H3eJqDF1VNcT(Hn zR!wBy{v+De9MMmxRF`_>tnCkPR*p&3Ys)o~nGS_bkVKUf<}ft#rFqI`HG`eaCs?!= zF~R?5>m5+;ER`(u*8A)OEePJfi)^e9n_*ucprsCR)RKgod+VUnO|?wF-Xb=w0CA?{ zw5<$r5s3-uU0go1L-@LI3Z!;7E?_~T@n(LoRPME@Rs4P1Uh;Y$Q|B{#Yd<^nvXURc z`kLcqkzcm0PmbV%x$2mJZKi?w1O_j3({P$W8bTt#-9+>0r1V?j6E@K$%zOB&o8)zA zv<>Tc!88f&lhIz606(`qJ|apUdPL!b>Uqs$^Y&6Q`p`%h!2R=2Pj2I2pY1nDL3aYg zDPb;vDk*?77T>KVq=|W}&>EFpERR0n92NBJ{{-+$TfVyoNi$<++_6A zHE5SV?(W0B<$auz1$x05 zMR66U@Eq2(#;#U>6asrO7!(wq*eQhKIdP+giO^MR1mb1{Hrv$vJ3JrrQDB!PFtS&? zic;ZiI`Xe@X=Yh(cxZZoaiQDdNUY;1cg=!3^Cwm%DF6@z(*W611}-htnNoa$Cth*^ zZc_M<=OsL{r~LYHXKgo&?QQ=W*bbC)@)irpgxJS31`WAaWP|2N3B0#kLRuwCZ9se^ z*9wxGqRGK*jb?mmK&+ihy{+3l^%Xl6=g~A^q6}vlr`<}n79ajl&MH9_B>5uri?~A2 zkZm-n#U$yMdm%XidD+67z+{fWgH7t&vu5t{j2(XM{ss)Fz0a$~!2Lhz`}UG~Icx&X zPY0%n9llIQ{JU=UoQOj*@r@= z%huJ{m;ja~46djLzwkEMZ}YOs2r{J2*(D1Gq{tq@(VRS9d*K!Iy^nK-iSwV=jHuP@Isj70&~PjNp%qV)vY&zrdEiOWSV_0>NTA0z;|KQ&w=yjo;6pI1g;iM;L!eZg#G%yeAti>BrB59 zX&KNUT4|>Ff*}dcAwY|1V}MLIYZvq=iUdoEiqP*kh#IQ*x3w<9iIg+04o^xDty6_r(p(Tr~Y+zW6kImP*k2|K;DtH(s#_L1ve9MNl1 z$fYD*P_*dPsmmuZDq4nAV&u>Jwq;g)*&`GeO1&)ZIG$Ca4Eo|Q{c^cidw6&$-&QE! zh4sTxUrE3VD{-$Wpl#KIx6@|XWn{Ou@Q1amf!y;S|Eth{anzIxM^&g^ugf@|C7gK0 zLI4mUlK-q{Dp$42qKR4&K3<%-O2@C{bRL#CB$9>r80*=e!kKNp2!>_NlM*3}GJ_eJ za%fC3p8nwxEuHQ+(yia($$NoI&w|a192Y;ucU1`yxFv%wo+M?lIDA3>3kI)oE{YSl zWCN|)kY=Sm+m|V}cOU2;*p7V^zJp6r6CY(b#tL^e9!W))DR`v2w#TMjDLx10Y+qRsQ%|v0ItjpLL^=CDA@+u0$)E$x# z2x+K*Unre05?36PhHNt}SZwEqX?M|`7zOGDsMORNb1sq$otK6jC%Cy6E}5YJ)>MPf zH{SF5+RUoFR;T-ocMcKHJQ$}&i6PjIsl!YT0;22p%R&gItxi_8Z#Uax6k3$ku!8DU z`dI=&$I1@^AW8%Z=2?maH2RDCY%?*@1NcLgy@NEbuz&G7 z5stUR!$EQ9(+r?Bn$|nJ59wEaKVl!0Vsi4&13|O?jmQ}(0?ZciPHC>Sqs@tk4;Eta> z^4&DoJF2dp8MR&8#*yz=Wt@z0eDfm@y>jR*(3XW?O`J{RbX)hrVeC_s)!5Q#OO!-} z=QsS?r@o3(_+`zQ7m-9~^kaO4l$;orkZO!ju>>2-JljHaWEdhR<&l`9*qg75cl^VQ zatFSI*~ejhAFUe~JsIY_3vS^m80_7T!%KaD?&Q}w+z#|)H`d#ewlJM&T#Y>B&NneG zo?gVoTbOcJvQR>8=Hk3d>Mq;KiJxPZqlpt{GEVbOPRG!J3Uf;)Y0_b_Q~8|H5Mqjx zukB>(k#SNyC7zCREBS{aY%X2}+f(hN(9_5Hv5V>y;AeE>@rF(aC7W$6p2y;juhqV{ znc|?^1?3~X>YxB=^Wr7y5{VE3nBrt*S3Ypu`jTgd4Y2~?zQ@E5Z;zTtUV;m@EnkTJ zBPNnTr(wjQJB3Rq{R^Xn&vbU5fjk}uKnOPdzF6(}1J|qin9ER?JA!KauhkdpuJ;_r z@IN@IF##<$i`_>pst($bYiqznj4I&7o5YSGlQ0|eDLTQFfkRNosS2960h*TcO)t^o zn1os;B3y#RqPnzaJ2~u%wuWu`W&I)|gmdar!<_Y>N)IkORFxA2zeMZcJe299OONv= z_D!c_u1y=1#vv6fOZl_=c7%GA$eX{Xhk)eEawAvG;w3rOyPAyIu!g9Xz>)SV=xvuk?tCl@03> zBd8BhA5-?0@LjHRmL&$;!8t7D%fy(t*db5JVt1@_Pn25fIS%d)+mSY1=>b$e9-g9b zJ^Q0qLkWZPyNr>ts~OD`O(LwgIaEE>S|QY_Xiq+Isb3tc{5mV|)Ud{E9Gi7NMl$Tg61k)&o}iNUkPLp#Ax?Zz-~6ANSHl(m1`knWynX}XU3@*+$6j8ogp%(ibue`f>I6_P0l8s*_^1 z_jUR7U3!ousGoZ62NmghD|mkJ{z{}uK%<3F;?m-SONRA_!J>~3RpcjiNT^D2+XW?a zJHhJ@@G_eh2v7J=tjOdBCG|^@njH~ja$J_1dYWkZUNV8-NuQWw#H62T!Z9h4cwHOue! zOK508B;_W5D1rhRP9S2aQ+a1tbBZ600U9M-D11^WM;Dvj9s86}ev>j67~G*@lr$N7 z5Io2TCL5-)s1*RyVCal;>~i6yNDf=UeV!!_ojg7~$o8gW*R_Ynz!nTa?C(U9q#I?W z>jRJC2=*gW$$WtIPf;U+m`*V9kOU{2!}cFCzfX$I{(YKr(P#H+u072uO5L6E#|>mp zPCz|BNs^Nq6W1%9#V~iy&Q+h5*Y15X=}EEjGPSK#&O0|&i$IQ;`bPdT%(%R2oUr-# zKQ?}(UPtLcuUdnV!Xi#~DAxE;8SCx><^OzXzA`~}Sb8R`#@>5X>^8EJT+x(nO%BRvP(VXO;{4+Y8knz5uzLc`irR_?bf&O+0+-iDo-Z8 zB{V;j$<&KzC4e>LF=qa$Rh4bh?^;5rEy13STc$Utle69|!>bw4+;6Hj4cRlkA4gtmU!drgq zac*Cg9am6;+TGk?%DvBgnal9|De9UnpWt#%saAwqv5j^Q#SuKd+TIb-azj0&YJ@PB z<8CzaJ<%$CSNFu&Hn69*x$Ir9L~&iFI!fHv6a5#{<0NZ}hN@}@jHuxn+PWPy%yTt)^hGOBcnWa@99-`T!^s9Tghn#?L7sOr|De*}P9Le82 z7*T=uyr`|41Q}AR*`yk8h5LeoKmeew`VwJF^fU4&-b69L=f%mb#8%(4al-2FFbX#g z>xHz9rV3YjF`HyE+kpdnE}o=b36@JXjbX3aGNF!3n1hS@H}DqVBL6@+yX2AK%@8&C z?R}f0$y=Y_QcIrY20G$XW<0%huME99=gt5}vBVh>T|=1xxT9T;2MKoGpI7rmSrRF2 z4z3z8VjAB2D?=Jozd5p{j41|Q6n~WE!8=MR&3(!d&&Vk^h5c2r|AiH&Y)?f`n)DxM zNY?r{$`nH@4)r4!ecKtK;OO?Uht-G2*hF6NKFs`W9KP3$JOXOQm%JJ0$jqZiJA(7~ z^(4jZbPOm7p6=o#4Pi*!Q1ts>5!xlBTE9Oqv$3{(*duRuFh?byvVD@PG-hCgO%O9S zG+oyQMcMMS6>utq5(?#?oG+Qz-+D)ro;b%dG>=N%=pzLU=8sjy8ZfM}2&M^q#{uP7 z)&FG}(*O>d50(%v{f)Nu?tqd$u^nKb!F^0qV)WPkEm0pm=}bco0-10TWo}gP7?m!^ zSrRNc@^lVz`pgjlSZRn6eW`|1)o2>kZmsZHfA}cEK_xN`I2W2}Qx`qApJkw%pir|S zz7Jgketa51%S|rqTrFs+8Kznl7lEV( zWWtML6*%7Fc9nNdTtk+f%oa03_0b0l34dI-RhKtwnZ!${oj~pMkaJHRUbMoD=;Mr9 zORKR#tsE^z9B!GQr*<2-$g9Dzy<8Ft1faH>8j`q94Nl%}H2BQbwzp`pK@i%5HDl7dNUn(p>HSXcV6NP}CAj() zV^k{yfo!j<-!s~BSciXqP=g|lICV?&$`>%}mwi)Ob`0Kg9kJND~qnoJH*lwbUw@RfCo zy7%PQ8`NA%zom``HE1h#LS zwd2q`g0e|lQGpUU(3TB0@I$AuPG^!rbGinJo3Be)z)&jn1n%$lMNRLms-aslGRT(2 zqwX;kj(YkR6`!x2N4ol%4>rRm(ht<(Wta5U8>E?kWFl3E&owiU<_8e@Rfed8c(mNH z@85U$>Dsa#=RpW>03D0~laql6lQ%REZttRg#>}3Vg# zpYlYhlOEPEV$Vi!EaDLrS`kr%d~Q>4OF!?TX9>JcLiBK?G(YmijQHI9>YrIG^y7WW ztogTA7k!sTMsw2l8-;>Lxjx%CuI?*fqo_;U%SQ4UN6J^V2L`FiR8xT=2~XWR6P{hD zL_hhbQm53LFc^LY*s{M-MXwlm&vauL?E&)BBGj7z72oI6J_Jk0RTGC7 z0{OG+^EaP0#7E?BAb)JQL;+6lOIRs$l>bWu^-Jrs|!gBkqf!V4krTv+pe4 zimc*-djDZ~FCcgo8@v;aZ~szydOo9o;L#-?BbGpE{m_W0{NVHjMc`3-mB{I&ZLw`r z-tjl(v*e(u(}bxTTd}-N7Cc}4Jh0y(`ZMFBjdz)$ETY$gd!GSL?RVrWOI9^cCY6X8W5VRh5DBlUn7^dX9s*_$gbF5d?xS4nyf z9@F@+FKWoFwBq=-IoCC=f+KSL*kAYZID6tR1C32XLzrv={qV^w0yX$X@ zi`WTo3EvcpbBZ$F2G@g;_@9;&U4_iU`Pd!&kK}5SNykN&fQe%ALXwn;1<2snf?_x@ zOOie-G`at-Txu;cj^24?kQ4DuDOhaEK7%OC5*%J}q*SyK{=*H8je zYW8$5&7=M+^}!#~Q)^#{r+yKjE?(KePfNqx4>N^Kht4ZMlBr^hX&)q=lrAuDAxWUq zKQZt74dN+CXi!%w!|#h4F4R+$l8f`maN=WMW~Fm}1sy$ZP>%lafrgr$>EWB}e1