Учебные примеры Excel: простой журнал, шаги ЕСЛИ и выпадающие списки

This commit is contained in:
apuc committed 2026-10-19 23:20:00 +03:00
1 parent 401b02b680
commit 63c4a5ff40
8 files changed
+314 -23

No files matched your search

+51 -14
View File
@@ -6,6 +6,7 @@ Uses the supplied PPTX theme/layouts, not a re-created logo.
from pathlib import Path
from datetime import datetime, timedelta
from collections import Counter
import argparse
import json
import re
import qrcode
@@ -26,11 +27,13 @@ DAY = datetime(2026, 10, 20)
TIMES = [60,60,60,120,120,120,120,120,120,180,360,120,60,120,180,120,120,180,420,180,120,60,90,60,30,300]
assert sum(TIMES) == 3600
DEPTS = ['Организационный отдел','Правовой отдел','Финансовый отдел','Отдел обращений','ИТ-отдел']
STATUSES = ['Выполнено','В работе','Новое']
CATEGORIES = ['Организация','Документы','Отчётность']
rows = []
for i in range(60):
k = i % 10
dept = DEPTS[i // 12]
category = ['Организация','Документы','Отчётность'][i % 3]
category = CATEGORIES[i % 3]
if k < 4:
deadline = DAY - timedelta(days=12-i % 5)
status = 'Выполнено'
@@ -44,10 +47,11 @@ for i in range(60):
rows.append([f'П-{i+1:03}',dept,category,deadline-timedelta(days=14),deadline,status,completed,control])
counts = Counter(r[7] for r in rows)
assert counts == {'Завершено':24,'Просрочено':18,'В срок':18}
assert all(r[1] in DEPTS and r[2] in CATEGORIES and r[5] in STATUSES for r in rows), 'значение вне выпадающего списка'
def build_workbook():
book = xlsxwriter.Workbook(OUT / 'excel-practice.xlsx')
book = xlsxwriter.Workbook(OUT / 'excel-practice.xlsx', {'strings_to_formulas': False})
book.set_properties({'title':'Учебный реестр поручений','author':'Бойко Кирилл','comments':'Синтетические данные. Не реальные сведения органа власти.'})
head = book.add_format({'bold':True,'bg_color':'#'+BLUE,'font_color':'white','text_wrap':True,'valign':'vcenter'})
normal = book.add_format({'font_name':'Calibri','font_size':12,'valign':'vcenter'})
@@ -61,7 +65,9 @@ def build_workbook():
inst.set_column('A:A',30); inst.set_column('B:B',100)
instructions = [
('Учебный материал','60 вымышленных поручений. Лекция 20.10.2026. Никаких реальных ПДн.'),
('Начало демонстрации','Сохраните копию книги. На листе Старт: Ctrl+T, заголовки, фильтр, заполнение формул H.'),
('Порядок демонстрации','Старт: простое условие (одно ЕСЛИ, без дат). Старт 2: вложенное условие (два ЕСЛИ, с датой среза). Реестр: готовый итог.'),
('Старт','Простое условие по статусу: =ЕСЛИ(F2="Выполнено";"Закрыто";"В работе"). Даты не используются.'),
('Старт 2','Вложенное условие: сначала статус, затем срок против Параметры!B2. Здесь появляется «Просрочено».'),
('Реестр','Готовая умная таблица и формулы H2:H61. Проверенный образец, не исходник для очистки.'),
('Параметры!B2','Фиксированная дата среза 20.10.2026. СЕГОДНЯ() намеренно не используется.'),
('Сводная','Вставка → Сводная таблица. Источник РеестрПоручений. Строки: Подразделение; столбцы: Контроль срока; значения: количество ID.'),
@@ -69,6 +75,7 @@ def build_workbook():
('Проверка','Ожидаемые итоги: 60 всего; 24 выполнено; 36 открыто; 18 открытых просрочено; 50% просроченных среди открытых.'),
('Смысл формулы H','Выполненные получают Завершено независимо от соблюдения срока. Проверка своевременности выполненных — отдельный расчёт.'),
('Границы примера','Выполненных с задержкой 12, без задержки 12. Учебный флаг просрочки считает только открытые.'),
('Выпадающие списки','Подразделение (B), Категория (C) и Статус (F) выбираются из списков на листе Параметры. Опечатки исключены.'),
('Не делать','Не перезаписывать оригинал при демонстрации, не отправлять реальные служебные данные внешней модели.'),
('Формулы в файле','Внутри XLSX функции сохранены на английском по стандарту. Excel показывает локализованные имена.'),
]
@@ -77,10 +84,32 @@ def build_workbook():
params = book.add_worksheet('Параметры');params.set_column('A:A',30);params.set_column('B:B',26)
params.write('A1','Параметр',head);params.write('B1','Значение',head)
params.write('A2','Дата среза',normal);params.write_datetime('B2',DAY,date)
params.write('A4','Допустимые статусы',head)
for j,v in enumerate(['Выполнено','В работе','Новое'],4):params.write(j,0,v,normal)
book.define_name('AllowedStatuses',"='Параметры'!$A$5:$A$7")
for name, ready in [('Старт',False),('Реестр',True)]:
# Источники выпадающих списков. Правятся здесь и сразу действуют на всех листах.
picklists=[('Допустимые статусы',STATUSES,3,'AllowedStatuses'),
('Допустимые подразделения',DEPTS,8,'AllowedDepartments'),
('Допустимые категории',CATEGORIES,15,'AllowedCategories')]
for label,values,head_row,name in picklists:
params.write(head_row,0,label,head)
params.set_row(head_row,22)
for k,v in enumerate(values):
params.write(head_row+1+k,0,v,normal)
params.set_row(head_row+k,20)
first,last=head_row+2,head_row+1+len(values)
book.define_name(name,f"='Параметры'!$A${first}:$A${last}")
def simple_formula(r): return f'=IF(F{r}="Выполнено","Закрыто","В работе")'
def nested_formula(r): return f'=IF(F{r}="Выполнено","Завершено",IF(E{r}<\'Параметры\'!$B$2,"Просрочено","В срок"))'
demo = [
('Старт', simple_formula, lambda row:'Закрыто' if row[5]=='Выполнено' else 'В работе',
'ШАГ 1. Простое условие: одно ЕСЛИ, только по статусу. Даты пока не используем.',
'=ЕСЛИ(F2="Выполнено";"Закрыто";"В работе")', False),
('Старт 2', nested_formula, lambda row:row[7],
'ШАГ 2. Вложенное условие: сначала статус, затем срок против даты среза из Параметры!B2.',
'=ЕСЛИ(F2="Выполнено";"Завершено";ЕСЛИ(E2<Параметры!$B$2;"Просрочено";"В срок"))', False),
('Реестр', nested_formula, lambda row:row[7],
'ГОТОВЫЙ ИТОГ. Умная таблица с формулами H2:H61 и подсветкой значений.',
'=ЕСЛИ(F2="Выполнено";"Завершено";ЕСЛИ(E2<Параметры!$B$2;"Просрочено";"В срок"))', True),
]
for name, formula, expected_value, note_text, formula_text, ready in demo:
sh=book.add_worksheet(name);sh.freeze_panes(1,2);sh.set_zoom(110)
for c,w in enumerate([12,29,19,19,19,18,19,20]):sh.set_column(c,c,w,normal)
sh.set_row(0,36)
@@ -90,14 +119,16 @@ def build_workbook():
for c,v in enumerate(row[:7]):
if isinstance(v,datetime):sh.write_datetime(n,c,v,date)
elif v is not None:sh.write(n,c,v,normal)
if ready:
formula=f'=IF(F{n+1}="Выполнено","Завершено",IF(E{n+1}<\'Параметры\'!$B$2,"Просрочено","В срок"))'
sh.write_formula(n,7,formula,normal,row[7])
sh.write_formula(n,7,formula(n+1),normal,expected_value(row))
if ready:
sh.add_table(0,0,60,7,{'name':'РеестрПоручений','style':'Table Style Medium 2','columns':[{'header':h} for h in headers]})
sh.conditional_format('H2:H61',{'type':'text','criteria':'containing','value':'Просрочено','format':alert})
sh.conditional_format('H2:H61',{'type':'text','criteria':'containing','value':'Завершено','format':ok})
sh.data_validation('F2:F1000',{'validate':'list','source':'=AllowedStatuses','error_title':'Выберите статус','error_message':'Используйте значение из списка.'})
sh.data_validation('B2:B1000',{'validate':'list','source':'=AllowedDepartments','error_title':'Выберите подразделение','error_message':'Используйте значение из списка.'})
sh.data_validation('C2:C1000',{'validate':'list','source':'=AllowedCategories','error_title':'Выберите категорию','error_message':'Используйте значение из списка.'})
sh.merge_range(62,0,62,7,note_text,wrap);sh.set_row(62,26)
sh.merge_range(63,0,63,7,'Формула: '+formula_text,wrap);sh.set_row(63,26)
sh.set_landscape();sh.fit_to_pages(1,0);sh.repeat_rows(0)
check=book.add_worksheet('Проверка');check.set_column('A:A',47);check.set_column('B:C',22)
check.write_row('A1',['Показатель','Расчёт','Ожидается'],head)
@@ -387,7 +418,13 @@ def build_slides():
if __name__=='__main__':
qr=qrcode.QRCode(error_correction=qrcode.constants.ERROR_CORRECT_M,box_size=16,border=4)
qr.add_data(URL);qr.make(fit=True);qr.make_image(fill_color='black',back_color='white').save(OUT/'telegram-qr.png')
build_workbook()
build_slides()
parser=argparse.ArgumentParser()
parser.add_argument('--only',choices=['all','workbook','slides'],default='all',
help='Rebuild everything, or only one artifact')
args=parser.parse_args()
if args.only in ('all','workbook'):
qr=qrcode.QRCode(error_correction=qrcode.constants.ERROR_CORRECT_M,box_size=16,border=4)
qr.add_data(URL);qr.make(fit=True);qr.make_image(fill_color='black',back_color='white').save(OUT/'telegram-qr.png')
build_workbook()
if args.only in ('all','slides'):
build_slides()