diff --git a/weather_forecast/__init__.py b/weather_forecast/__init__.py new file mode 100644 index 0000000..5c8679b --- /dev/null +++ b/weather_forecast/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- + +# from . import controllers +from . import models +from . import wizard diff --git a/weather_forecast/__manifest__.py b/weather_forecast/__manifest__.py new file mode 100644 index 0000000..df1f5c3 --- /dev/null +++ b/weather_forecast/__manifest__.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +{ + 'name': "Dự báo thời tiết", + + 'summary': "Dự báo thời tiết theo dựa theo Open Weather.", + + 'description': """ + - Dự báo thời tiết theo ngày (3 ngày tiếp theo), theo giờ (3 khung giờ trong ngày). + - Tạo báo cáo (PDF) thời tiết cho 3 ngày tiếp theo. + - Tự động gửi email nhắc nhở mang áo mưa tới nhân viên nếu ngày mai tỉ lệ mưa > 50%. + """, + + 'author': "Chicken_dev", + 'website': "https://www.yourcompany.com", + + 'category': 'Weather', + 'version': '0.1', + + 'depends': ['base'], + + 'data': [ + 'security/ir.model.access.csv', + 'views/weather_api_views.xml', + 'views/menu.xml', + 'wizard/res_config_settings_views.xml', + ], +} + diff --git a/weather_forecast/controllers/__init__.py b/weather_forecast/controllers/__init__.py new file mode 100644 index 0000000..b0f26a9 --- /dev/null +++ b/weather_forecast/controllers/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import controllers diff --git a/weather_forecast/controllers/controllers.py b/weather_forecast/controllers/controllers.py new file mode 100644 index 0000000..0916c42 --- /dev/null +++ b/weather_forecast/controllers/controllers.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# from odoo import http + + +# class WeatherForecast(http.Controller): +# @http.route('/weather_forecast/weather_forecast', auth='public') +# def index(self, **kw): +# return "Hello, world" + +# @http.route('/weather_forecast/weather_forecast/objects', auth='public') +# def list(self, **kw): +# return http.request.render('weather_forecast.listing', { +# 'root': '/weather_forecast/weather_forecast', +# 'objects': http.request.env['weather_forecast.weather_forecast'].search([]), +# }) + +# @http.route('/weather_forecast/weather_forecast/objects/', auth='public') +# def object(self, obj, **kw): +# return http.request.render('weather_forecast.object', { +# 'object': obj +# }) + diff --git a/weather_forecast/demo/demo.xml b/weather_forecast/demo/demo.xml new file mode 100644 index 0000000..0b2a193 --- /dev/null +++ b/weather_forecast/demo/demo.xml @@ -0,0 +1,30 @@ + + + + + diff --git a/weather_forecast/models/__init__.py b/weather_forecast/models/__init__.py new file mode 100644 index 0000000..13e56ca --- /dev/null +++ b/weather_forecast/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import weather_api diff --git a/weather_forecast/models/weather_api.py b/weather_forecast/models/weather_api.py new file mode 100644 index 0000000..ff2f37e --- /dev/null +++ b/weather_forecast/models/weather_api.py @@ -0,0 +1,192 @@ +from odoo import fields, models, api, _ +from odoo.exceptions import UserError +import requests +from collections import defaultdict +from datetime import datetime + +class WeatherAPI(models.Model): + _name = 'weather.api' + _description = 'Weather API' + _rec_name = 'company_id' + + company_id = fields.Many2one('res.company', string='Công ty', required=True, default=lambda self: self.env.company) + + current_temperature = fields.Float(string="Nhiệt độ hiện tại", compute="_compute_weather", store=False) + current_humidity = fields.Float(string="Độ ẩm hiện tại", compute="_compute_weather", store=False) + current_wind_speed = fields.Float(string="Gió hiện tại", compute="_compute_weather", store=False) + current_description = fields.Char(string="Tình trạng thời tiết", compute="_compute_weather", store=False) + + today_hourly_first = fields.Html(string="1h - 8h", compute="_compute_weather", store=False) + today_hourly_second = fields.Html(string="9h - 16h", compute="_compute_weather", store=False) + today_hourly_third = fields.Html(string="17h - 24h", compute="_compute_weather", store=False) + + next_3_days_weather = fields.Html(string="Dự báo 3 ngày", compute="_compute_weather", store=False) + + @api.depends() + def _compute_weather(self): + for rec in self: + current = rec.get_weather_current() + rec.current_temperature = current.get('temperature') + rec.current_humidity = current.get('humidity') + rec.current_wind_speed = current.get('wind_speed') + rec.current_description = current.get('description') + + # Weather by time blocks + today = rec._get_today_weather() + rec.today_hourly_first = rec._format_html(today['first']) + rec.today_hourly_second = rec._format_html(today['second']) + rec.today_hourly_third = rec._format_html(today['third']) + + # Next 3 days + forecast = rec._get_next_3days_forecast() + html = "" + rec.next_3_days_weather = html + + def _format_html(self, data): + if not data: + return "

Không có dữ liệu.

" + html = "" + return html + + + # Lấy thông tin API key từ cấu hình + @api.model + def get_api_key(self): + """Lấy thông tin API key từ cấu hình""" + api_key = self.env['ir.config_parameter'].sudo().get_param('weather_infor.api_key') + if not api_key: + raise UserError("API Key chưa được cấu hình. Vui lòng vào Settings > Weather API Settings để nhập API Key.") + return api_key + + + # Lấy thông tin thành phố từ cấu hình công ty + @api.model + def get_city(self): + """Lấy thông tin thành phố từ cấu hình công ty""" + city = self.env.company.city + if not city: + raise UserError(_("Thành phố chưa được cấu hình. Vui lòng vào Settings > Công ty để nhập thành phố.")) + return city + + + # Lấy thông tin thời tiết hiện tại + @api.model + def get_weather_current(self): + """Lấy dữ liệu thời tiết hiện tại""" + api_key = self.get_api_key() + city = self.get_city() + url = f"https://api.openweathermap.org/data/2.5/weather?q={city}&appid={api_key}&units=metric" + + response = requests.get(url) + if response.status_code == 200: + data = response.json() + return { + 'temperature': data['main']['temp'], + 'humidity': data['main']['humidity'], + 'wind_speed': data['wind']['speed'], + 'description': data['weather'][0]['description'] + } + else: + raise UserError(_("Không thể lấy dữ liệu thời tiết. Vui lòng kiểm tra lại API key hoặc tên thành phố.")) + + + # Lấy thông tin thời tiết 3 ngày + @api.model + def _get_next_3days_forecast(self): + """Lấy dữ liệu thời tiết cho 3 ngày tiếp theo""" + api_key = self.get_api_key() + city = self.get_city() + url = f"https://api.openweathermap.org/data/2.5/forecast?q={city}&appid={api_key}&units=metric" + + response = requests.get(url) + if response.status_code != 200: + raise UserError(_("Không thể lấy dữ liệu thời tiết. Vui lòng kiểm tra lại API key hoặc tên thành phố.")) + + data = response.json() + forecast_data = data['list'] + + # Gom dữ liệu theo ngày + weather_data = defaultdict(list) + for forecast in forecast_data: + dt_txt = forecast['dt_txt'] + date_str = dt_txt.split(' ')[0] + weather_data[date_str].append(forecast) + + # Chọn 3 ngày tiếp theo + sorted_dates = sorted(weather_data.keys()) + today = datetime.now().date() + if today in sorted_dates: + sorted_dates.remove(today) + next_3_days = sorted_dates[1:4] + + result = [] + for date in next_3_days: + daily_forecasts = weather_data[date] + temps = [f['main']['temp'] for f in daily_forecasts] + humidities = [f['main']['humidity'] for f in daily_forecasts] + descriptions = [f['weather'][0]['description'] for f in daily_forecasts] + wind_speeds = [f['wind']['speed'] for f in daily_forecasts] + + result.append({ + 'date': date, + 'temp_min': min(temps), + 'temp_max': max(temps), + 'humidity': round(sum(humidities) / len(humidities), 2), + 'wind_speed': round(sum(wind_speeds) / len(wind_speeds), 2), + 'main_description': max(set(descriptions), key=descriptions.count), # mô tả xuất hiện nhiều nhất + }) + return result + + + # Lấy thông tin thời tiết ngày hôm nay, chia làm 3 khung giờ + @api.model + def _get_today_weather(self): + """Lấy dữ liệu thời tiết ngày hôm nay chia làm 3 khung giờ: 1-8h, 9-16h, 17-24h""" + api_key = self.get_api_key() + city = self.env.company.city + url = f"https://api.openweathermap.org/data/2.5/forecast?q={city}&appid={api_key}&units=metric" + + response = requests.get(url) + if response.status_code != 200: + raise UserError(_("Không thể lấy dữ liệu thời tiết. Vui lòng kiểm tra lại API key hoặc tên thành phố.")) + + data = response.json() + forecast_data = data['list'] + + today_str = datetime.now().strftime('%Y-%m-%d') + hourly_data = { + 'first': [], # 1-8h + 'second': [], # 9-16h + 'third': [] # 17-24h + } + + for forecast in forecast_data: + dt_txt = forecast['dt_txt'] + date_part, time_part = dt_txt.split(' ') + if date_part != today_str: + continue # chỉ lấy dữ liệu của ngày hôm nay + + hour = int(time_part.split(':')[0]) + weather_info = { + 'hour': hour, + 'date': date_part, + 'temp_min': forecast['main']['temp_min'], + 'temp_max': forecast['main']['temp_max'], + 'humidity': forecast['main']['humidity'], + 'wind_speed': forecast['wind']['speed'], + 'description': forecast['weather'][0]['description'] + } + + if 0 <= hour <= 8: + hourly_data['first'].append(weather_info) + elif 9 <= hour <= 16: + hourly_data['second'].append(weather_info) + else: + hourly_data['third'].append(weather_info) + return hourly_data diff --git a/weather_forecast/security/ir.model.access.csv b/weather_forecast/security/ir.model.access.csv new file mode 100644 index 0000000..08ca290 --- /dev/null +++ b/weather_forecast/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_weather_api,weather_api,model_weather_api,base.group_user,1,1,1,1 diff --git a/weather_forecast/views/menu.xml b/weather_forecast/views/menu.xml new file mode 100644 index 0000000..63cb1a1 --- /dev/null +++ b/weather_forecast/views/menu.xml @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/weather_forecast/views/weather_api_views.xml b/weather_forecast/views/weather_api_views.xml new file mode 100644 index 0000000..4699784 --- /dev/null +++ b/weather_forecast/views/weather_api_views.xml @@ -0,0 +1,79 @@ + + + + + weather.api.view.form + weather.api + +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + weather.api.view.tree + weather.api + + + + + + + + + Dự báo thời tiết + weather.api + list,form + +

Không có dự báo thời tiết trong danh sách này!

+
+
+ +
\ No newline at end of file diff --git a/weather_forecast/wizard/__init__.py b/weather_forecast/wizard/__init__.py new file mode 100644 index 0000000..aba2228 --- /dev/null +++ b/weather_forecast/wizard/__init__.py @@ -0,0 +1 @@ +from . import res_config_settings \ No newline at end of file diff --git a/weather_forecast/wizard/res_config_settings.py b/weather_forecast/wizard/res_config_settings.py new file mode 100644 index 0000000..0930e4a --- /dev/null +++ b/weather_forecast/wizard/res_config_settings.py @@ -0,0 +1,6 @@ +from odoo import fields, models, api, _ + +class ResConfigSettings(models.TransientModel): + _inherit = 'res.config.settings' + + weather_api_key = fields.Char(string="OpenWeatherMap API Key", config_parameter="weather_infor.api_key") \ No newline at end of file diff --git a/weather_forecast/wizard/res_config_settings_views.xml b/weather_forecast/wizard/res_config_settings_views.xml new file mode 100644 index 0000000..bfbd272 --- /dev/null +++ b/weather_forecast/wizard/res_config_settings_views.xml @@ -0,0 +1,36 @@ + + + + + res.config.settings.view.form.inherit.weather.infor + res.config.settings + + + + + + +
+
+ +
+
+
+
+
+
+
+ + + Weather API Settings + res.config.settings + form + inline + {'module' : 'weather_infor', 'bin_size': False} + + + + +
\ No newline at end of file