def patch_courier(courier_id, new_data: dict, couriers_db: Couriers, orders_db: Orders): """ Change data for courier with courier_id and remove orders from assigned orders, if they don't fit by region, delivery hours or weight anymore """ courier_id = int(courier_id) courier = couriers_db.get_item(courier_id) # Get assigned orders' data and create copy of its list assigned_orders_ids = get_ids(courier['assigned_orders']) assigned_orders = orders_db.get_items_by_ids(assigned_orders_ids) new_assigned_orders = deepcopy(assigned_orders) # Remove orders, that don't fit anymore, from assigned_orders if 'regions' in new_data: new_assigned_orders = list( filter(lambda o: o['region'] in new_data['regions'], new_assigned_orders)) if 'working_hours' in new_data: new_assigned_orders = list( filter( lambda o: _is_intervals_fitted(new_data['working_hours'], o[ 'intervals']), new_assigned_orders)) if 'courier_type' in new_data and \ COURIERS_CAPACITY[new_data['courier_type']] < COURIERS_CAPACITY[courier['courier_type']]: # if capacity of courier get down new_capacity = _get_courier_capacity(new_data['courier_type'], new_assigned_orders) new_assigned_orders = _replace_orders(new_assigned_orders, new_capacity) # Add edited assigned list to new data and edit courier data in DB new_data['assigned_orders'] = [{ 'id': order['id'] } for order in new_assigned_orders] couriers_db.edit_item(courier_id, new_data) # Update status to 0 (not assigned) for orders that are not in new assigned orders dropped_orders = [{ 'id': order['id'] } for order in assigned_orders if order not in new_assigned_orders] orders_db.update_status(dropped_orders, 0, courier_type='') # Get edited courier's data from DB edited_courier = couriers_db.get_item(courier_id) return { 'courier_id': edited_courier['_id'], 'courier_type': edited_courier['courier_type'], 'regions': edited_courier['regions'], 'working_hours': edited_courier['working_hours'] }
def assign_orders(courier_id_data: dict, couriers_db: Couriers, orders_db: Orders): """ Find orders that fit by region, delivery hours and put max number of them to assigned_orders. """ courier_id = courier_id_data['courier_id'] courier = couriers_db.get_item(courier_id) assigned_orders_ids = get_ids( courier['assigned_orders'] ) # ids of orders that already in courier's bag # if courier has assigned orders, return them if len(assigned_orders_ids) != 0: courier = couriers_db.get_item(courier_id) return { 'orders': [{ 'id': o['id'] } for o in courier['assigned_orders']], 'assign_time': courier['assign_time'].isoformat() } # Find orders that fit time intervals intervals = parse_intervals( courier['working_hours']) # get intervals in seconds orders_to_place = [] for interval in intervals: fitted_orders = orders_db.get_fitted_orders(interval[0], interval[1], courier['regions']) orders_to_place = update_orders(orders_to_place, fitted_orders) # Put all possible new orders to courier's bag courier_type = courier['courier_type'] capacity = COURIERS_CAPACITY[courier['courier_type']] assigned_orders = _place_orders(orders_to_place, capacity) # Write assigned orders to DB (if it is empty list, it is already in DB) and update their statuses if len(assigned_orders) != 0: couriers_db.write_assigned_orders(courier_id, assigned_orders, assign_time=datetime.now()) orders_db.update_status(assigned_orders, 1, courier_type=courier_type) # Get assigned orders from DB courier = couriers_db.get_item(courier_id) return { 'orders': [{ 'id': o['id'] } for o in courier['assigned_orders']], 'assign_time': courier['assign_time'].isoformat() }
def get_courier(courier_id, couriers_db: Couriers, orders_db: Orders): """ Return 'courier_id', 'courier_type', 'regions', 'working_hours'. If courier has completed orders, return 'rating' and 'earning' too """ courier_id = int(courier_id) courier = couriers_db.get_item(courier_id) completed_orders_ids = get_ids(courier['completed_orders']) # If no completed orders, no rating and earning returns if len(completed_orders_ids) == 0: return { 'courier_id': courier_id, 'courier_type': courier['courier_type'], 'regions': courier['regions'], 'working_hours': courier['working_hours'] } completed_orders = orders_db.get_items_by_ids(completed_orders_ids) rating, earning = _calculate_rating_and_earning(completed_orders) return { 'courier_id': courier_id, 'courier_type': courier['courier_type'], 'regions': courier['regions'], 'working_hours': courier['working_hours'], 'rating': rating, 'earning': earning }
def complete_order(complete_data: dict, couriers_db: Couriers, orders_db: Orders): """ Move order with id=complete_data['order_id'] from assigned_orders to completed_orders and change its status """ courier_id = complete_data['courier_id'] order_id = complete_data['order_id'] complete_time = str_to_datetime(complete_data['complete_time']) courier_type = couriers_db.get_item(courier_id)['courier_type'] delivery_time = _calculate_delivery_time(courier_id, complete_time, couriers_db, orders_db) # Update data of completed order in DB orders_db.update_status([{ 'id': order_id }], 2, complete_time=complete_time, delivery_time=delivery_time) # Change status of order in couriers' DB couriers_db.move_order_to_completed(courier_id, order_id) return {'order_id': order_id}
def validate_complete_orders(complete_data: dict, couriers_db: Couriers, orders_db: Orders) -> (dict, int): # Check if data matches the schema with open('application/schemas/OrdersCompletePostRequest.json') as f: complete_order_schema = ValidatorWithDatetime(json.load(f)) for err in complete_order_schema.iter_errors(complete_data): return {'validation_error': err.message}, 400 # Check if courier_id and order_id exist courier_id = complete_data['courier_id'] order_id = complete_data['order_id'] if not couriers_db.get_item(courier_id): return {'validation_error': f'There are no courier with id {courier_id}'}, 400 elif not orders_db.get_item(order_id): return {'validation_error': f'There are no order with id {order_id}'}, 400 # Check status of the order order_status = orders_db.get_item(order_id)['status'] if order_status == 0: return {'validation_error': f'order with id {order_id} is not assigned'}, 400 elif order_status == 2: return {'validation_error': f'order with id {order_id} is already completed'}, 400 # Check that complete time is later than assign time courier = couriers_db.get_item(courier_id) assign_time = courier['assign_time'] complete_time = str_to_datetime(complete_data['complete_time']) if complete_time < assign_time: return {'validation_error': f'\'complete_time\' must be later than \'assign_time\': {assign_time.isoformat()}'}, 400 # Check if the order is assigned to this courier assigned_orders = courier['assigned_orders'] if {'id': order_id} not in assigned_orders: return { 'validation_error': f'order with id {order_id} is assigned to another courier'}, 400 return complete_data, 200
def _calculate_delivery_time(courier_id: int, complete_time: datetime, couriers_db: Couriers, orders_db: Orders) -> float: """ Return delta between complete_time of order with order_id and complete_time of previous order or assign time (if new assigning occurs after the last completed order) """ courier = couriers_db.get_item(courier_id) completed_orders_ids = get_ids(courier['completed_orders']) assign_time: datetime = courier['assign_time'] # If there are no completed orders, return delta with assign time if len(completed_orders_ids) == 0: return (complete_time - assign_time).total_seconds() completed_orders = orders_db.get_items_by_ids(completed_orders_ids) previous_time = _find_previous_time(assign_time, completed_orders) return (complete_time - previous_time).total_seconds()
def make_app(db: Database) -> Flask: """ Create service 'Slasti' """ app = Flask(__name__) couriers_db = Couriers(db['couriers']) orders_db = Orders(db['orders']) @app.route('/couriers', methods=['POST']) def add_couriers() -> Response: """ Get couriers data as request, save it to couriers_db and return response with added ids """ if not request.is_json: return validator.bad_header couriers_data = request.get_json() data, status = validator.validate_couriers(couriers_data) if status == 201: data = post_couriers(data, couriers_db) return Response(json.dumps(data), status, headers={'Content-Type': 'application/json'}) @app.route('/couriers/<courier_id>', methods=['PATCH']) def update_courier(courier_id) -> Response: """ Get new data for courier with courier_id, save it to couriers_db and return response with new data """ if not request.is_json: return validator.bad_header new_data = request.get_json() data, status = validator.validate_update_courier( new_data, courier_id, couriers_db) if status == 200: data = patch_courier(courier_id, new_data, couriers_db, orders_db) return Response(json.dumps(data), status, headers={'Content-Type': 'application/json'}) @app.route('/orders', methods=['POST']) def add_orders() -> Response: """ Get orders data as request, save it to orders_db and return response with added ids """ if not request.is_json: return validator.bad_header orders_data = request.get_json() data, status = validator.validate_orders(orders_data) if status == 201: data = post_orders(data, orders_db) return Response(json.dumps(data), status, headers={'Content-Type': 'application/json'}) @app.route('/orders/assign', methods=['POST']) def post_orders_assign() -> Response: """ Get json with courier_id, find optimal orders and assign it to him """ if not request.is_json: return validator.bad_header courier_id_data = request.get_json() data, status = validator.validate_assign_orders( courier_id_data, couriers_db) if status == 200: data = assign_orders(courier_id_data, couriers_db, orders_db) return Response(json.dumps(data), status, headers={'Content-Type': 'application/json'}) @app.route('/orders/complete', methods=['POST']) def post_order_complete() -> Response: """ Get json with courier_id, order_id and complete time, and complete this orders """ if not request.is_json: return validator.bad_header complete_data = request.get_json() data, status = validator.validate_complete_orders( complete_data, couriers_db, orders_db) if status == 200: data = complete_order(data, couriers_db, orders_db) return Response(json.dumps(data), status, headers={'Content-Type': 'application/json'}) @app.route('/couriers/<courier_id>', methods=['GET']) def get_courier_info(courier_id) -> Response: """ Return info about courier with courier_id """ data, status = validator.validate_courier_id(courier_id, couriers_db) if status == 200: data = get_courier(courier_id, couriers_db, orders_db) return Response(json.dumps(data), status=200, headers={'Content-Type': 'application/json'}) return app
def post_orders(orders_data: dict, orders_db: Orders): """ Add orders to DB """ posted_ids = orders_db.add_items(orders_data['data']) return {'orders': [{'id': i} for i in posted_ids]}