jk
Browse files- Post_management.py +15 -6
- __pycache__/Post_management.cpython-311.pyc +0 -0
- __pycache__/functi.cpython-311.pyc +0 -0
- __pycache__/line_db.cpython-311.pyc +0 -0
- __pycache__/timing_lin.cpython-311.pyc +0 -0
- app.css +55 -1
- app.py +1 -1
- functi.py +81 -22
- line_db.py +8 -2
- timing_lin.py +7 -7
Post_management.py
CHANGED
@@ -6,13 +6,22 @@ days_of_week = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturda
|
|
6 |
Social_network_menu = ["Linkedin"]
|
7 |
|
8 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9 |
with tgb.Page(class_name="bodyp") as Post_manag :
|
10 |
with tgb.part(class_name="source_body") :
|
11 |
tgb.text("Post Management", class_name="Title_Page")
|
12 |
with tgb.part(class_name="layout_top") :
|
13 |
tgb.text("Linking Account", class_name="header-burgundy")
|
14 |
with tgb.layout(columns="1", class_name="table_t") : # Single column for full width
|
15 |
-
|
|
|
16 |
tgb.input("{Linked_account_name}", label="Account name", change_delay=-1, action_on_blur=True, width="100%")
|
17 |
tgb.selector("{Linked_social_network}", lov=Social_network_menu, dropdown=True, label="Select platform", width="100%")
|
18 |
tgb.button(label="Add account", on_action=authen, class_name="btn-burgundy", width="100%")
|
@@ -23,22 +32,22 @@ with tgb.Page(class_name="bodyp") as Post_manag :
|
|
23 |
tgb.input("{generated_post}", multiline=True, class_name="burgundy-border", width="100%")
|
24 |
tgb.button(label="Publish", on_action=post_publishing, width="100%")
|
25 |
with tgb.part(class_name="table_s") :
|
26 |
-
tgb.text("Post Scheduling", class_name="header-burgundy")
|
27 |
# Texte explicatif juste en dessous du titre, avec exemple
|
28 |
tgb.text(
|
29 |
"⏰ Pour garantir la bonne planification, l'heure choisie doit être au moins 10 minutes après l'heure actuelle. "
|
30 |
"Par exemple, si il est 14h00, choisissez au minimum 14h10. "
|
31 |
"Sinon, la tâche sera programmée pour la prochaine occurrence du créneau choisi.",
|
32 |
class_name="info-message"
|
33 |
-
|
34 |
)
|
35 |
-
with tgb.layout(columns="1", class_name="table_t") :
|
36 |
-
|
|
|
37 |
tgb.input("{time_value_hour}", label="Hour", change_delay=-1, action_on_blur=True, width="100%")
|
38 |
tgb.input("{time_value_minute}", label="Minute", change_delay=-1, action_on_blur=True, width="100%")
|
39 |
tgb.selector("{day_value}", lov=days_of_week, multiple=True, dropdown=True, show_select_all=True, width="100%")
|
40 |
tgb.button(label="Add", on_action=add_scheduling, width="100%")
|
41 |
-
tgb.table("{data_schedule}", editable=True, on_add=False, on_edit=False, on_delete=delete_schedule, class_name="table_t", width="100%")
|
42 |
|
43 |
# Date complète
|
44 |
# tgb.date("{day_value}", format="EEEE, MMM dd, yyyy", label="Full Date with Day")
|
|
|
6 |
Social_network_menu = ["Linkedin"]
|
7 |
|
8 |
|
9 |
+
from taipy import Gui
|
10 |
+
import taipy.gui.builder as tgb
|
11 |
+
from functi import post_generation,post_publishing,authen,add_scheduling,delete_account,delete_schedule
|
12 |
+
|
13 |
+
days_of_week = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
|
14 |
+
Social_network_menu = ["Linkedin"]
|
15 |
+
|
16 |
+
|
17 |
with tgb.Page(class_name="bodyp") as Post_manag :
|
18 |
with tgb.part(class_name="source_body") :
|
19 |
tgb.text("Post Management", class_name="Title_Page")
|
20 |
with tgb.part(class_name="layout_top") :
|
21 |
tgb.text("Linking Account", class_name="header-burgundy")
|
22 |
with tgb.layout(columns="1", class_name="table_t") : # Single column for full width
|
23 |
+
# Mobile responsive: 3 columns on desktop, 1 column on mobile
|
24 |
+
with tgb.layout(columns="1 1 1", columns_mobile="1", gap="16px") :
|
25 |
tgb.input("{Linked_account_name}", label="Account name", change_delay=-1, action_on_blur=True, width="100%")
|
26 |
tgb.selector("{Linked_social_network}", lov=Social_network_menu, dropdown=True, label="Select platform", width="100%")
|
27 |
tgb.button(label="Add account", on_action=authen, class_name="btn-burgundy", width="100%")
|
|
|
32 |
tgb.input("{generated_post}", multiline=True, class_name="burgundy-border", width="100%")
|
33 |
tgb.button(label="Publish", on_action=post_publishing, width="100%")
|
34 |
with tgb.part(class_name="table_s") :
|
35 |
+
tgb.text("Post Scheduling", class_name="header-burgundy")
|
36 |
# Texte explicatif juste en dessous du titre, avec exemple
|
37 |
tgb.text(
|
38 |
"⏰ Pour garantir la bonne planification, l'heure choisie doit être au moins 10 minutes après l'heure actuelle. "
|
39 |
"Par exemple, si il est 14h00, choisissez au minimum 14h10. "
|
40 |
"Sinon, la tâche sera programmée pour la prochaine occurrence du créneau choisi.",
|
41 |
class_name="info-message"
|
|
|
42 |
)
|
43 |
+
with tgb.layout(columns="1", class_name="table_t ") :
|
44 |
+
# Mobile responsive: 4 columns on desktop, 1 column on mobile
|
45 |
+
with tgb.layout(columns="1 1 1 1", columns_mobile="1", gap="16px") :
|
46 |
tgb.input("{time_value_hour}", label="Hour", change_delay=-1, action_on_blur=True, width="100%")
|
47 |
tgb.input("{time_value_minute}", label="Minute", change_delay=-1, action_on_blur=True, width="100%")
|
48 |
tgb.selector("{day_value}", lov=days_of_week, multiple=True, dropdown=True, show_select_all=True, width="100%")
|
49 |
tgb.button(label="Add", on_action=add_scheduling, width="100%")
|
50 |
+
tgb.table("{data_schedule}", editable=True, on_add=False, on_edit=False, on_delete=delete_schedule, class_name="table_t", width="100%", columns="social_network;schedule_time")
|
51 |
|
52 |
# Date complète
|
53 |
# tgb.date("{day_value}", format="EEEE, MMM dd, yyyy", label="Full Date with Day")
|
__pycache__/Post_management.cpython-311.pyc
CHANGED
Binary files a/__pycache__/Post_management.cpython-311.pyc and b/__pycache__/Post_management.cpython-311.pyc differ
|
|
__pycache__/functi.cpython-311.pyc
CHANGED
Binary files a/__pycache__/functi.cpython-311.pyc and b/__pycache__/functi.cpython-311.pyc differ
|
|
__pycache__/line_db.cpython-311.pyc
CHANGED
Binary files a/__pycache__/line_db.cpython-311.pyc and b/__pycache__/line_db.cpython-311.pyc differ
|
|
__pycache__/timing_lin.cpython-311.pyc
CHANGED
Binary files a/__pycache__/timing_lin.cpython-311.pyc and b/__pycache__/timing_lin.cpython-311.pyc differ
|
|
app.css
CHANGED
@@ -134,4 +134,58 @@ html, body {
|
|
134 |
font-size: 1.3em;
|
135 |
padding: 15px 20px;
|
136 |
}
|
137 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
134 |
font-size: 1.3em;
|
135 |
padding: 15px 20px;
|
136 |
}
|
137 |
+
}
|
138 |
+
|
139 |
+
@media (max-width: 768px) {
|
140 |
+
.source_body {
|
141 |
+
margin: 1em;
|
142 |
+
padding: 0.5em;
|
143 |
+
}
|
144 |
+
|
145 |
+
.layout_top {
|
146 |
+
padding-top: 2em;
|
147 |
+
}
|
148 |
+
|
149 |
+
.table_s, .table_t {
|
150 |
+
padding-top: 1em;
|
151 |
+
}
|
152 |
+
|
153 |
+
/* Make inputs and buttons more touch-friendly on mobile */
|
154 |
+
input, button, select {
|
155 |
+
min-height: 44px; /* Apple's recommended touch target size */
|
156 |
+
font-size: 16px; /* Prevents zoom on iOS */
|
157 |
+
}
|
158 |
+
|
159 |
+
/* Improve table readability on mobile */
|
160 |
+
.taipy-table {
|
161 |
+
font-size: 14px;
|
162 |
+
}
|
163 |
+
|
164 |
+
/* Better text wrapping for info messages */
|
165 |
+
.info-message {
|
166 |
+
font-size: 14px;
|
167 |
+
line-height: 1.4;
|
168 |
+
margin-bottom: 1em;
|
169 |
+
}
|
170 |
+
|
171 |
+
/* Ensure burgundy borders are visible on mobile */
|
172 |
+
.burgundy-border {
|
173 |
+
border-width: 1px;
|
174 |
+
margin-bottom: 1em;
|
175 |
+
}
|
176 |
+
}
|
177 |
+
|
178 |
+
/* Improve button styling for mobile */
|
179 |
+
@media (max-width: 480px) {
|
180 |
+
.source_body {
|
181 |
+
margin: 0.5em;
|
182 |
+
}
|
183 |
+
|
184 |
+
.Title_Page {
|
185 |
+
font-size: 1.5em;
|
186 |
+
}
|
187 |
+
|
188 |
+
.header-burgundy {
|
189 |
+
font-size: 1.1em;
|
190 |
+
}
|
191 |
+
}
|
app.py
CHANGED
@@ -185,7 +185,7 @@ if __name__ == "__main__":
|
|
185 |
gui = Gui(pages=pages)
|
186 |
gui.run(
|
187 |
debug=True,
|
188 |
-
port=7860,host = "0.0.0.0",
|
189 |
stylekit=stylekit,
|
190 |
title="Lin",
|
191 |
dark_mode=False,
|
|
|
185 |
gui = Gui(pages=pages)
|
186 |
gui.run(
|
187 |
debug=True,
|
188 |
+
# port=7860,host = "0.0.0.0",
|
189 |
stylekit=stylekit,
|
190 |
title="Lin",
|
191 |
dark_mode=False,
|
functi.py
CHANGED
@@ -86,7 +86,10 @@ def post_generation_for_robot(id,social,idd) :
|
|
86 |
code=id,
|
87 |
api_name="/poster_linkedin"
|
88 |
)
|
89 |
-
|
|
|
|
|
|
|
90 |
except Exception as e:
|
91 |
print("Erreur dans gen():", e, flush=True)
|
92 |
|
@@ -101,8 +104,7 @@ def post_publishing_for_robot(id_social,id_user,idd,ss) :
|
|
101 |
token_value = first["token"]
|
102 |
sub_value = first["sub"]
|
103 |
post = dd["Text_content"].iloc[0]
|
104 |
-
|
105 |
-
print("⏳ Tâche planifiée pour gfjfxd",flush = True)
|
106 |
|
107 |
url = "https://api.linkedin.com/v2/ugcPosts"
|
108 |
headers = {
|
@@ -110,26 +112,82 @@ def post_publishing_for_robot(id_social,id_user,idd,ss) :
|
|
110 |
"X-Restli-Protocol-Version": "2.0.0",
|
111 |
"Content-Type": "application/json"
|
112 |
}
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
"
|
122 |
-
|
123 |
-
|
124 |
-
"visibility": {
|
125 |
-
"com.linkedin.ugc.MemberNetworkVisibility": "PUBLIC"
|
126 |
}
|
127 |
}
|
128 |
-
|
129 |
-
|
130 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
131 |
except Exception as e:
|
132 |
-
|
|
|
|
|
|
|
133 |
|
134 |
def planifier_ligne(id_schedule, id_social, user_id, schedule_time_str, ss, adjusted_time):
|
135 |
# Parse schedule_time_str and adjusted_time
|
@@ -208,7 +266,7 @@ def add_scheduling(state):
|
|
208 |
jour, horaire = timesche.split()
|
209 |
horaire = horaire.replace(';', ':')
|
210 |
h, m = map(int, horaire.split(':'))
|
211 |
-
m -=
|
212 |
final_time = f"{jour} {h}:{m:02d}"
|
213 |
|
214 |
# Add to database
|
@@ -230,7 +288,7 @@ def add_scheduling(state):
|
|
230 |
jour, horaire = timesche.split()
|
231 |
horaire = horaire.replace(';', ':')
|
232 |
h, m = map(int, horaire.split(':'))
|
233 |
-
m -=
|
234 |
final_time = f"{jour} {h}:{m:02d}"
|
235 |
|
236 |
# Add to database
|
@@ -293,6 +351,7 @@ def post_generation(state) :
|
|
293 |
code=state.user_inf.user.id,
|
294 |
api_name="/poster_linkedin"
|
295 |
)
|
|
|
296 |
|
297 |
def authen(state) :
|
298 |
if state.Linked_social_network == "Linkedin" :
|
|
|
86 |
code=id,
|
87 |
api_name="/poster_linkedin"
|
88 |
)
|
89 |
+
generated_post = eval(generated_post)
|
90 |
+
print(generated_post,flush = True)
|
91 |
+
print(generated_post[1],flush = True)
|
92 |
+
db_manager.add_post(social,generated_post[0],idd)
|
93 |
except Exception as e:
|
94 |
print("Erreur dans gen():", e, flush=True)
|
95 |
|
|
|
104 |
token_value = first["token"]
|
105 |
sub_value = first["sub"]
|
106 |
post = dd["Text_content"].iloc[0]
|
107 |
+
img = dd["image_content_url"].iloc[0]
|
|
|
108 |
|
109 |
url = "https://api.linkedin.com/v2/ugcPosts"
|
110 |
headers = {
|
|
|
112 |
"X-Restli-Protocol-Version": "2.0.0",
|
113 |
"Content-Type": "application/json"
|
114 |
}
|
115 |
+
|
116 |
+
|
117 |
+
if img is not None:
|
118 |
+
register_body = {
|
119 |
+
"registerUploadRequest": {
|
120 |
+
"recipes": ["urn:li:digitalmediaRecipe:feedshare-image"],
|
121 |
+
"owner": sub_value,
|
122 |
+
"serviceRelationships": [{
|
123 |
+
"relationshipType": "OWNER",
|
124 |
+
"identifier": "urn:li:userGeneratedContent"
|
125 |
+
}]
|
|
|
|
|
126 |
}
|
127 |
}
|
128 |
+
r = requests.post("https://api.linkedin.com/v2/assets?action=registerUpload",
|
129 |
+
headers=headers, json=register_body)
|
130 |
+
datar = r.json()["value"]
|
131 |
+
upload_url = datar["uploadMechanism"]["com.linkedin.digitalmedia.uploading.MediaUploadHttpRequest"]["uploadUrl"]
|
132 |
+
asset_urn = datar["asset"]
|
133 |
+
|
134 |
+
upload_headers = {
|
135 |
+
"Authorization": f"Bearer {token_value}",
|
136 |
+
"X-Restli-Protocol-Version": "2.0.0",
|
137 |
+
"Content-Type": "application/octet-stream"
|
138 |
+
}
|
139 |
+
response = requests.put(upload_url, headers=upload_headers, data=img)
|
140 |
+
if response.status_code not in (200,201):
|
141 |
+
print("Erreur upload:", response.status_code, response.text)
|
142 |
+
|
143 |
+
post_body = {
|
144 |
+
"author": sub_value,
|
145 |
+
"lifecycleState": "PUBLISHED",
|
146 |
+
"specificContent": {
|
147 |
+
"com.linkedin.ugc.ShareContent": {
|
148 |
+
"shareCommentary": {"text": post},
|
149 |
+
"shareMediaCategory": "IMAGE",
|
150 |
+
"media": [{
|
151 |
+
"status": "READY",
|
152 |
+
"media": asset_urn,
|
153 |
+
"description": {"text": "Une belle image"},
|
154 |
+
"title": {"text": "Titre image"}
|
155 |
+
}]
|
156 |
+
}
|
157 |
+
},
|
158 |
+
"visibility": {"com.linkedin.ugc.MemberNetworkVisibility": "PUBLIC"}
|
159 |
+
}
|
160 |
+
resp = requests.post("https://api.linkedin.com/v2/ugcPosts",
|
161 |
+
headers=headers, json=post_body)
|
162 |
+
print(resp.status_code, resp.text)
|
163 |
+
else:
|
164 |
+
|
165 |
+
print("⏳ Tâche planifiée pour gfjfxd",flush = True)
|
166 |
+
|
167 |
+
|
168 |
+
body = {
|
169 |
+
"author": f"urn:li:person:{sub_value}",
|
170 |
+
"lifecycleState": "PUBLISHED",
|
171 |
+
"specificContent": {
|
172 |
+
"com.linkedin.ugc.ShareContent": {
|
173 |
+
"shareCommentary": {
|
174 |
+
"text": post
|
175 |
+
},
|
176 |
+
"shareMediaCategory": "NONE"
|
177 |
+
}
|
178 |
+
},
|
179 |
+
"visibility": {
|
180 |
+
"com.linkedin.ugc.MemberNetworkVisibility": "PUBLIC"
|
181 |
+
}
|
182 |
+
}
|
183 |
+
resp = requests.post(url, headers=headers, json=body)
|
184 |
+
db_manager.update_post(id_social,idd)
|
185 |
+
print([resp.status_code, resp.text],flush = True)
|
186 |
except Exception as e:
|
187 |
+
print("Erreur dans post():", e, flush=True)
|
188 |
+
|
189 |
+
|
190 |
+
|
191 |
|
192 |
def planifier_ligne(id_schedule, id_social, user_id, schedule_time_str, ss, adjusted_time):
|
193 |
# Parse schedule_time_str and adjusted_time
|
|
|
266 |
jour, horaire = timesche.split()
|
267 |
horaire = horaire.replace(';', ':')
|
268 |
h, m = map(int, horaire.split(':'))
|
269 |
+
m -= 5 # 7 minutes before for generation
|
270 |
final_time = f"{jour} {h}:{m:02d}"
|
271 |
|
272 |
# Add to database
|
|
|
288 |
jour, horaire = timesche.split()
|
289 |
horaire = horaire.replace(';', ':')
|
290 |
h, m = map(int, horaire.split(':'))
|
291 |
+
m -= 5 # 7 minutes before for generation
|
292 |
final_time = f"{jour} {h}:{m:02d}"
|
293 |
|
294 |
# Add to database
|
|
|
351 |
code=state.user_inf.user.id,
|
352 |
api_name="/poster_linkedin"
|
353 |
)
|
354 |
+
state.generated_post = state.generated_post[0]
|
355 |
|
356 |
def authen(state) :
|
357 |
if state.Linked_social_network == "Linkedin" :
|
line_db.py
CHANGED
@@ -2,6 +2,8 @@ import os
|
|
2 |
from supabase import create_client, Client
|
3 |
from pandas import json_normalize
|
4 |
import pandas
|
|
|
|
|
5 |
|
6 |
class DatabaseManager:
|
7 |
|
@@ -99,10 +101,14 @@ class DatabaseManager:
|
|
99 |
"sub" : data["sub"],"given_name" : data["given_name"],"family_name" : data["family_name"],"picture" : data["picture"] })
|
100 |
.execute()
|
101 |
)
|
102 |
-
def add_post(self,id_social,content,ids,tg : bool =False) :
|
|
|
|
|
|
|
|
|
103 |
response = (
|
104 |
self.supabase.table("Post_content")
|
105 |
-
.insert({"id_social": id_social,"Text_content" :content ,"is_published" : tg,"sched" : ids })
|
106 |
.execute())
|
107 |
|
108 |
def update_post(self,ids,idd):
|
|
|
2 |
from supabase import create_client, Client
|
3 |
from pandas import json_normalize
|
4 |
import pandas
|
5 |
+
import io
|
6 |
+
from PIL import Image
|
7 |
|
8 |
class DatabaseManager:
|
9 |
|
|
|
101 |
"sub" : data["sub"],"given_name" : data["given_name"],"family_name" : data["family_name"],"picture" : data["picture"] })
|
102 |
.execute()
|
103 |
)
|
104 |
+
def add_post(self,id_social,content,ids,img: Image.Image = None,tg : bool =False) :
|
105 |
+
if img :
|
106 |
+
buffer = io.BytesIO()
|
107 |
+
img.save(buffer, format="JPEG")
|
108 |
+
img = buffer.getvalue()
|
109 |
response = (
|
110 |
self.supabase.table("Post_content")
|
111 |
+
.insert({"id_social": id_social,"Text_content" :content ,"is_published" : tg,"sched" : ids,"image_content_url" : img})
|
112 |
.execute())
|
113 |
|
114 |
def update_post(self,ids,idd):
|
timing_lin.py
CHANGED
@@ -21,7 +21,7 @@ def format_slot(dt):
|
|
21 |
|
22 |
def add_request(df_fixed, new_slot_str):
|
23 |
new_dt = parse_slot(new_slot_str)
|
24 |
-
desired_dt = new_dt - timedelta(minutes=
|
25 |
|
26 |
# on récupère les adjusted existants triés
|
27 |
slots_dt = sorted(parse_slot(s) for s in df_fixed['adjusted_time'])
|
@@ -34,21 +34,21 @@ def add_request(df_fixed, new_slot_str):
|
|
34 |
conflict = False
|
35 |
|
36 |
# 1) avec le schedule_time
|
37 |
-
if new_dt - final_dt < timedelta(minutes=
|
38 |
-
# on recule pour avoir exactement
|
39 |
-
final_dt = new_dt - timedelta(minutes=
|
40 |
conflict = True
|
41 |
|
42 |
# 2) avec chaque adjusted existant
|
43 |
for slot in slots_dt:
|
44 |
-
if abs((slot - final_dt).total_seconds()) <
|
45 |
# on recule de 7min par rapport à cet adjusted
|
46 |
# si slot > final, on repousse final à slot -7min
|
47 |
# sinon (slot < final), on recule encore de 7min
|
48 |
if slot > final_dt:
|
49 |
-
final_dt = slot - timedelta(minutes=
|
50 |
else:
|
51 |
-
final_dt = slot - timedelta(minutes=
|
52 |
conflict = True
|
53 |
break
|
54 |
|
|
|
21 |
|
22 |
def add_request(df_fixed, new_slot_str):
|
23 |
new_dt = parse_slot(new_slot_str)
|
24 |
+
desired_dt = new_dt - timedelta(minutes=5)
|
25 |
|
26 |
# on récupère les adjusted existants triés
|
27 |
slots_dt = sorted(parse_slot(s) for s in df_fixed['adjusted_time'])
|
|
|
34 |
conflict = False
|
35 |
|
36 |
# 1) avec le schedule_time
|
37 |
+
if new_dt - final_dt < timedelta(minutes=5):
|
38 |
+
# on recule pour avoir exactement 7mi d'avance
|
39 |
+
final_dt = new_dt - timedelta(minutes=5)
|
40 |
conflict = True
|
41 |
|
42 |
# 2) avec chaque adjusted existant
|
43 |
for slot in slots_dt:
|
44 |
+
if abs((slot - final_dt).total_seconds()) < 5*60:
|
45 |
# on recule de 7min par rapport à cet adjusted
|
46 |
# si slot > final, on repousse final à slot -7min
|
47 |
# sinon (slot < final), on recule encore de 7min
|
48 |
if slot > final_dt:
|
49 |
+
final_dt = slot - timedelta(minutes=5)
|
50 |
else:
|
51 |
+
final_dt = slot - timedelta(minutes=5)
|
52 |
conflict = True
|
53 |
break
|
54 |
|