Учебные примеры Excel: простой журнал, шаги ЕСЛИ и выпадающие списки
This commit is contained in:
1 parent
401b02b680
commit
63c4a5ff40
8 files changed
+314
-23
No files matched your search
+51
-14
@@ -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()
|
||||
Reference in new issue
Block a user