fnschool 20250109.80531.837__py3-none-any.whl → 20251011.81638.854__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of fnschool might be problematic. Click here for more details.

Files changed (278) hide show
  1. fnschoo1/__init__.py +53 -0
  2. fnschoo1/canteen/admin.py +3 -0
  3. fnschoo1/canteen/apps.py +6 -0
  4. fnschoo1/canteen/forms.py +84 -0
  5. fnschoo1/canteen/migrations/0001_initial.py +119 -0
  6. fnschoo1/canteen/migrations/0002_ingredient_is_disabled.py +20 -0
  7. fnschoo1/canteen/migrations/0003_consumption_is_disabled_alter_ingredient_is_disabled.py +23 -0
  8. fnschoo1/canteen/migrations/0004_alter_ingredient_name_category_and_more.py +66 -0
  9. fnschoo1/canteen/migrations/0005_alter_category_created_at_alter_category_name_and_more.py +41 -0
  10. fnschoo1/canteen/migrations/0006_category_is_disabled_alter_category_user_and_more.py +49 -0
  11. fnschoo1/canteen/migrations/0007_alter_consumption_amount_used_and_more.py +30 -0
  12. fnschoo1/canteen/migrations/0008_category_abbreviation_mealtype.py +67 -0
  13. fnschoo1/canteen/migrations/0009_alter_category_abbreviation_and_more.py +55 -0
  14. fnschoo1/canteen/migrations/0010_alter_consumption_options_alter_ingredient_options_and_more.py +215 -0
  15. fnschoo1/canteen/migrations/0011_category_pin_to_consumptions_top.py +23 -0
  16. fnschoo1/canteen/migrations/0012_alter_ingredient_storage_date.py +18 -0
  17. fnschoo1/canteen/models.py +179 -0
  18. fnschoo1/canteen/templates/canteen/category/create.html +17 -0
  19. fnschoo1/canteen/templates/canteen/category/delete.html +61 -0
  20. fnschoo1/canteen/templates/canteen/category/list.html +63 -0
  21. fnschoo1/canteen/templates/canteen/category/update.html +23 -0
  22. fnschoo1/canteen/templates/canteen/close.html +11 -0
  23. fnschoo1/canteen/templates/canteen/consumption/_create.html +19 -0
  24. fnschoo1/canteen/templates/canteen/consumption/create.html +456 -0
  25. fnschoo1/canteen/templates/canteen/ingredient/close.html +11 -0
  26. fnschoo1/canteen/templates/canteen/ingredient/create.html +19 -0
  27. fnschoo1/canteen/templates/canteen/ingredient/create_one.html +17 -0
  28. fnschoo1/canteen/templates/canteen/ingredient/delete.html +41 -0
  29. fnschoo1/canteen/templates/canteen/ingredient/list.html +128 -0
  30. fnschoo1/canteen/templates/canteen/ingredient/update.html +23 -0
  31. fnschoo1/canteen/templates/canteen/meal_type/create.html +17 -0
  32. fnschoo1/canteen/templates/canteen/meal_type/delete.html +56 -0
  33. fnschoo1/canteen/templates/canteen/meal_type/list.html +59 -0
  34. fnschoo1/canteen/templates/canteen/meal_type/update.html +23 -0
  35. fnschoo1/canteen/tests.py +3 -0
  36. fnschoo1/canteen/urls.py +116 -0
  37. fnschoo1/canteen/views.py +814 -0
  38. fnschoo1/canteen/workbook/generate.py +2098 -0
  39. fnschoo1/db.sqlite3 +0 -0
  40. fnschoo1/fnschool/__init__.py +13 -0
  41. fnschoo1/fnschool/asgi.py +16 -0
  42. fnschoo1/fnschool/settings.py +167 -0
  43. fnschoo1/fnschool/templatetags/fnschool_tags.py +27 -0
  44. fnschoo1/fnschool/urls.py +30 -0
  45. fnschoo1/fnschool/views.py +9 -0
  46. fnschoo1/fnschool/wsgi.py +16 -0
  47. fnschoo1/locale/en/LC_MESSAGES/django.mo +0 -0
  48. fnschoo1/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
  49. fnschoo1/manage.py +25 -0
  50. fnschoo1/profiles/admin.py +27 -0
  51. fnschoo1/profiles/apps.py +12 -0
  52. fnschoo1/profiles/forms.py +67 -0
  53. fnschoo1/profiles/migrations/0001_initial.py +192 -0
  54. fnschoo1/profiles/migrations/0002_alter_profile_bio.py +20 -0
  55. fnschoo1/profiles/migrations/0003_alter_profile_options_alter_profile_address_and_more.py +67 -0
  56. fnschoo1/profiles/migrations/0004_profile_gender.py +26 -0
  57. fnschoo1/profiles/migrations/0005_alter_profile_gender.py +23 -0
  58. fnschoo1/profiles/models.py +60 -0
  59. fnschoo1/profiles/signals.py +20 -0
  60. fnschoo1/profiles/templates/profiles/create.html +16 -0
  61. fnschoo1/profiles/templates/profiles/detail.html +14 -0
  62. fnschoo1/profiles/templates/profiles/edit.html +12 -0
  63. fnschoo1/profiles/templates/profiles/log_in.html +20 -0
  64. fnschoo1/profiles/templates/profiles/log_out.html +12 -0
  65. fnschoo1/profiles/tests.py +3 -0
  66. fnschoo1/profiles/urls.py +15 -0
  67. fnschoo1/profiles/views.py +63 -0
  68. fnschoo1/static/css/bootstrap.min.css +11776 -0
  69. fnschoo1/static/css/fnschool.css +26 -0
  70. fnschoo1/static/images/favicon.ico +0 -0
  71. fnschoo1/static/js/bootstrap.bundle.min.js +4223 -0
  72. fnschoo1/static/js/bootstrap.min.js +2919 -0
  73. fnschoo1/static/js/fnschool.js +84 -0
  74. fnschoo1/static/js/jquery.min.js +5413 -0
  75. fnschoo1/static/js/jquery.slim.min.js +4331 -0
  76. fnschoo1/static/js/popper.min.js +1306 -0
  77. fnschoo1/static_collected/admin/css/autocomplete.css +377 -0
  78. fnschoo1/static_collected/admin/css/base.css +1224 -0
  79. fnschoo1/static_collected/admin/css/changelists.css +334 -0
  80. fnschoo1/static_collected/admin/css/dark_mode.css +136 -0
  81. fnschoo1/static_collected/admin/css/dashboard.css +30 -0
  82. fnschoo1/static_collected/admin/css/forms.css +546 -0
  83. fnschoo1/static_collected/admin/css/login.css +62 -0
  84. fnschoo1/static_collected/admin/css/nav_sidebar.css +145 -0
  85. fnschoo1/static_collected/admin/css/responsive.css +1043 -0
  86. fnschoo1/static_collected/admin/css/responsive_rtl.css +84 -0
  87. fnschoo1/static_collected/admin/css/rtl.css +311 -0
  88. fnschoo1/static_collected/admin/css/vendor/select2/select2.css +708 -0
  89. fnschoo1/static_collected/admin/css/vendor/select2/select2.min.css +1 -0
  90. fnschoo1/static_collected/admin/css/widgets.css +639 -0
  91. fnschoo1/static_collected/admin/js/SelectBox.js +128 -0
  92. fnschoo1/static_collected/admin/js/SelectFilter2.js +503 -0
  93. fnschoo1/static_collected/admin/js/actions.js +232 -0
  94. fnschoo1/static_collected/admin/js/admin/DateTimeShortcuts.js +496 -0
  95. fnschoo1/static_collected/admin/js/admin/RelatedObjectLookups.js +276 -0
  96. fnschoo1/static_collected/admin/js/autocomplete.js +33 -0
  97. fnschoo1/static_collected/admin/js/calendar.js +251 -0
  98. fnschoo1/static_collected/admin/js/cancel.js +29 -0
  99. fnschoo1/static_collected/admin/js/change_form.js +21 -0
  100. fnschoo1/static_collected/admin/js/collapse.js +43 -0
  101. fnschoo1/static_collected/admin/js/core.js +174 -0
  102. fnschoo1/static_collected/admin/js/filters.js +37 -0
  103. fnschoo1/static_collected/admin/js/inlines.js +439 -0
  104. fnschoo1/static_collected/admin/js/jquery.init.js +8 -0
  105. fnschoo1/static_collected/admin/js/nav_sidebar.js +81 -0
  106. fnschoo1/static_collected/admin/js/popup_response.js +24 -0
  107. fnschoo1/static_collected/admin/js/prepopulate.js +43 -0
  108. fnschoo1/static_collected/admin/js/prepopulate_init.js +20 -0
  109. fnschoo1/static_collected/admin/js/theme.js +57 -0
  110. fnschoo1/static_collected/admin/js/urlify.js +529 -0
  111. fnschoo1/static_collected/admin/js/vendor/jquery/jquery.js +10913 -0
  112. fnschoo1/static_collected/admin/js/vendor/jquery/jquery.min.js +2 -0
  113. fnschoo1/static_collected/admin/js/vendor/select2/i18n/af.js +43 -0
  114. fnschoo1/static_collected/admin/js/vendor/select2/i18n/ar.js +36 -0
  115. fnschoo1/static_collected/admin/js/vendor/select2/i18n/az.js +33 -0
  116. fnschoo1/static_collected/admin/js/vendor/select2/i18n/bg.js +38 -0
  117. fnschoo1/static_collected/admin/js/vendor/select2/i18n/bn.js +39 -0
  118. fnschoo1/static_collected/admin/js/vendor/select2/i18n/bs.js +48 -0
  119. fnschoo1/static_collected/admin/js/vendor/select2/i18n/ca.js +41 -0
  120. fnschoo1/static_collected/admin/js/vendor/select2/i18n/cs.js +62 -0
  121. fnschoo1/static_collected/admin/js/vendor/select2/i18n/da.js +37 -0
  122. fnschoo1/static_collected/admin/js/vendor/select2/i18n/de.js +41 -0
  123. fnschoo1/static_collected/admin/js/vendor/select2/i18n/dsb.js +51 -0
  124. fnschoo1/static_collected/admin/js/vendor/select2/i18n/el.js +43 -0
  125. fnschoo1/static_collected/admin/js/vendor/select2/i18n/en.js +41 -0
  126. fnschoo1/static_collected/admin/js/vendor/select2/i18n/es.js +41 -0
  127. fnschoo1/static_collected/admin/js/vendor/select2/i18n/et.js +38 -0
  128. fnschoo1/static_collected/admin/js/vendor/select2/i18n/eu.js +45 -0
  129. fnschoo1/static_collected/admin/js/vendor/select2/i18n/fa.js +42 -0
  130. fnschoo1/static_collected/admin/js/vendor/select2/i18n/fi.js +42 -0
  131. fnschoo1/static_collected/admin/js/vendor/select2/i18n/fr.js +43 -0
  132. fnschoo1/static_collected/admin/js/vendor/select2/i18n/gl.js +40 -0
  133. fnschoo1/static_collected/admin/js/vendor/select2/i18n/he.js +44 -0
  134. fnschoo1/static_collected/admin/js/vendor/select2/i18n/hi.js +40 -0
  135. fnschoo1/static_collected/admin/js/vendor/select2/i18n/hr.js +45 -0
  136. fnschoo1/static_collected/admin/js/vendor/select2/i18n/hsb.js +51 -0
  137. fnschoo1/static_collected/admin/js/vendor/select2/i18n/hu.js +44 -0
  138. fnschoo1/static_collected/admin/js/vendor/select2/i18n/hy.js +40 -0
  139. fnschoo1/static_collected/admin/js/vendor/select2/i18n/id.js +36 -0
  140. fnschoo1/static_collected/admin/js/vendor/select2/i18n/is.js +37 -0
  141. fnschoo1/static_collected/admin/js/vendor/select2/i18n/it.js +43 -0
  142. fnschoo1/static_collected/admin/js/vendor/select2/i18n/ja.js +40 -0
  143. fnschoo1/static_collected/admin/js/vendor/select2/i18n/ka.js +42 -0
  144. fnschoo1/static_collected/admin/js/vendor/select2/i18n/km.js +38 -0
  145. fnschoo1/static_collected/admin/js/vendor/select2/i18n/ko.js +42 -0
  146. fnschoo1/static_collected/admin/js/vendor/select2/i18n/lt.js +45 -0
  147. fnschoo1/static_collected/admin/js/vendor/select2/i18n/lv.js +41 -0
  148. fnschoo1/static_collected/admin/js/vendor/select2/i18n/mk.js +42 -0
  149. fnschoo1/static_collected/admin/js/vendor/select2/i18n/ms.js +38 -0
  150. fnschoo1/static_collected/admin/js/vendor/select2/i18n/nb.js +38 -0
  151. fnschoo1/static_collected/admin/js/vendor/select2/i18n/ne.js +44 -0
  152. fnschoo1/static_collected/admin/js/vendor/select2/i18n/nl.js +46 -0
  153. fnschoo1/static_collected/admin/js/vendor/select2/i18n/pl.js +43 -0
  154. fnschoo1/static_collected/admin/js/vendor/select2/i18n/ps.js +41 -0
  155. fnschoo1/static_collected/admin/js/vendor/select2/i18n/pt-BR.js +39 -0
  156. fnschoo1/static_collected/admin/js/vendor/select2/i18n/pt.js +41 -0
  157. fnschoo1/static_collected/admin/js/vendor/select2/i18n/ro.js +43 -0
  158. fnschoo1/static_collected/admin/js/vendor/select2/i18n/ru.js +48 -0
  159. fnschoo1/static_collected/admin/js/vendor/select2/i18n/sk.js +61 -0
  160. fnschoo1/static_collected/admin/js/vendor/select2/i18n/sl.js +41 -0
  161. fnschoo1/static_collected/admin/js/vendor/select2/i18n/sq.js +43 -0
  162. fnschoo1/static_collected/admin/js/vendor/select2/i18n/sr-Cyrl.js +48 -0
  163. fnschoo1/static_collected/admin/js/vendor/select2/i18n/sr.js +48 -0
  164. fnschoo1/static_collected/admin/js/vendor/select2/i18n/sv.js +40 -0
  165. fnschoo1/static_collected/admin/js/vendor/select2/i18n/th.js +36 -0
  166. fnschoo1/static_collected/admin/js/vendor/select2/i18n/tk.js +36 -0
  167. fnschoo1/static_collected/admin/js/vendor/select2/i18n/tr.js +40 -0
  168. fnschoo1/static_collected/admin/js/vendor/select2/i18n/uk.js +59 -0
  169. fnschoo1/static_collected/admin/js/vendor/select2/i18n/vi.js +37 -0
  170. fnschoo1/static_collected/admin/js/vendor/select2/i18n/zh-CN.js +36 -0
  171. fnschoo1/static_collected/admin/js/vendor/select2/i18n/zh-TW.js +33 -0
  172. fnschoo1/static_collected/admin/js/vendor/select2/select2.full.js +7115 -0
  173. fnschoo1/static_collected/admin/js/vendor/select2/select2.full.min.js +2 -0
  174. fnschoo1/static_collected/admin/js/vendor/xregexp/xregexp.js +4993 -0
  175. fnschoo1/static_collected/admin/js/vendor/xregexp/xregexp.min.js +160 -0
  176. fnschoo1/static_collected/css/bootstrap.min.css +11776 -0
  177. fnschoo1/static_collected/css/fnschool.css +26 -0
  178. fnschoo1/static_collected/images/favicon.ico +0 -0
  179. fnschoo1/static_collected/js/bootstrap.bundle.min.js +4223 -0
  180. fnschoo1/static_collected/js/bootstrap.min.js +2919 -0
  181. fnschoo1/static_collected/js/fnschool.js +84 -0
  182. fnschoo1/static_collected/js/jquery.min.js +5413 -0
  183. fnschoo1/static_collected/js/jquery.slim.min.js +4331 -0
  184. fnschoo1/static_collected/js/popper.min.js +1306 -0
  185. fnschoo1/templates/base/_css.html +1 -0
  186. fnschoo1/templates/base/_js.html +15 -0
  187. fnschoo1/templates/base/content.html +30 -0
  188. fnschoo1/templates/base/header_content_footer.html +35 -0
  189. fnschoo1/templates/close.html +11 -0
  190. fnschoo1/templates/home.html +51 -0
  191. fnschoo1/templates/includes/_footer.html +39 -0
  192. fnschoo1/templates/includes/_header.html +77 -0
  193. fnschoo1/templates/includes/_navigation.html +0 -0
  194. fnschoo1/templates/includes/_paginator.html +27 -0
  195. fnschoo1/templates/registration/logged_out.html +0 -0
  196. fnschoo1/templates/registration/login.html +0 -0
  197. fnschool-20251011.81638.854.dist-info/METADATA +179 -0
  198. fnschool-20251011.81638.854.dist-info/RECORD +209 -0
  199. {fnschool-20250109.80531.837.dist-info → fnschool-20251011.81638.854.dist-info}/WHEEL +1 -1
  200. fnschool-20251011.81638.854.dist-info/entry_points.txt +2 -0
  201. fnschool-20251011.81638.854.dist-info/top_level.txt +1 -0
  202. fnschool/__init__.py +0 -35
  203. fnschool/__main__.py +0 -16
  204. fnschool/app.py +0 -103
  205. fnschool/canteen/__init__.py +0 -3
  206. fnschool/canteen/__main__.py +0 -3
  207. fnschool/canteen/bill.py +0 -253
  208. fnschool/canteen/canteen.py +0 -1
  209. fnschool/canteen/canteen.toml +0 -61
  210. fnschool/canteen/config.py +0 -10
  211. fnschool/canteen/consuming.py +0 -53
  212. fnschool/canteen/currency.py +0 -17
  213. fnschool/canteen/data/bill.i18n.xlsx +0 -0
  214. fnschool/canteen/data/bill.xlsx +0 -0
  215. fnschool/canteen/data/consuming.xlsx +0 -0
  216. fnschool/canteen/data/purchase_list.xlsx +0 -0
  217. fnschool/canteen/entry.py +0 -40
  218. fnschool/canteen/food.py +0 -206
  219. fnschool/canteen/food_classes.py +0 -33
  220. fnschool/canteen/food_classes.toml +0 -64
  221. fnschool/canteen/operator.py +0 -91
  222. fnschool/canteen/path.py +0 -28
  223. fnschool/canteen/spreadsheet/base.py +0 -213
  224. fnschool/canteen/spreadsheet/consuming.py +0 -310
  225. fnschool/canteen/spreadsheet/consumingsum.py +0 -76
  226. fnschool/canteen/spreadsheet/cover.py +0 -64
  227. fnschool/canteen/spreadsheet/ctspreadsheet.py +0 -351
  228. fnschool/canteen/spreadsheet/food.py +0 -680
  229. fnschool/canteen/spreadsheet/inventory.py +0 -375
  230. fnschool/canteen/spreadsheet/merging.py +0 -340
  231. fnschool/canteen/spreadsheet/preconsuming.py +0 -329
  232. fnschool/canteen/spreadsheet/purchasing.py +0 -885
  233. fnschool/canteen/spreadsheet/purchasingsum.py +0 -110
  234. fnschool/canteen/spreadsheet/spreadsheet.py +0 -363
  235. fnschool/canteen/spreadsheet/translating.py +0 -12
  236. fnschool/canteen/spreadsheet/unwarehousing.py +0 -178
  237. fnschool/canteen/spreadsheet/unwarehousingsum.py +0 -11
  238. fnschool/canteen/spreadsheet/warehousing.py +0 -360
  239. fnschool/canteen/spreadsheet/workbook.py +0 -17
  240. fnschool/canteen/test.py +0 -97
  241. fnschool/config.py +0 -48
  242. fnschool/entry.py +0 -67
  243. fnschool/exam/__init__.py +0 -8
  244. fnschool/exam/data/parental_emails.xlsx +0 -0
  245. fnschool/exam/data/score.xlsx +0 -0
  246. fnschool/exam/email.py +0 -349
  247. fnschool/exam/entry.py +0 -36
  248. fnschool/exam/language.py +0 -19
  249. fnschool/exam/path.py +0 -24
  250. fnschool/exam/score.py +0 -1191
  251. fnschool/exam/subject.py +0 -20
  252. fnschool/exam/teacher.py +0 -54
  253. fnschool/external.py +0 -89
  254. fnschool/games/__init__.py +0 -1
  255. fnschool/games/__main__.py +0 -1
  256. fnschool/games/games.py +0 -1
  257. fnschool/inoutput.py +0 -97
  258. fnschool/language.py +0 -40
  259. fnschool/locales/en_US/LC_MESSAGES/fnschool.mo +0 -0
  260. fnschool/locales/zh_CN/LC_MESSAGES/fnschool.mo +0 -0
  261. fnschool/locales/zh_HK/LC_MESSAGES/fnschool.mo +0 -0
  262. fnschool/locales/zh_SG/LC_MESSAGES/fnschool.mo +0 -0
  263. fnschool/locales/zh_TW/LC_MESSAGES/fnschool.mo +0 -0
  264. fnschool/path.py +0 -45
  265. fnschool/test.py +0 -24
  266. fnschool/user.py +0 -262
  267. fnschool-20250109.80531.837.dist-info/METADATA +0 -342
  268. fnschool-20250109.80531.837.dist-info/RECORD +0 -78
  269. fnschool-20250109.80531.837.dist-info/entry_points.txt +0 -5
  270. fnschool-20250109.80531.837.dist-info/top_level.txt +0 -1
  271. /fnschool/canteen/consume.py → /fnschoo1/canteen/__init__.py +0 -0
  272. /fnschool/canteen/food_recount.toml → /fnschoo1/canteen/migrations/__init__.py +0 -0
  273. {fnschool/canteen/spreadsheet → fnschoo1/canteen/workbook}/__init__.py +0 -0
  274. /fnschool/exam/__main__.py → /fnschoo1/fnschool/templatetags/__init__.py +0 -0
  275. /fnschool/canteen/food_recounts.toml → /fnschoo1/profiles/__init__.py +0 -0
  276. /fnschool/canteen/warehouse.py → /fnschoo1/profiles/migrations/__init__.py +0 -0
  277. /fnschool/canteen/workbook.toml → /fnschoo1/templates/base/_content.html +0 -0
  278. {fnschool-20250109.80531.837.dist-info → fnschool-20251011.81638.854.dist-info/licenses}/LICENSE +0 -0
@@ -1,885 +0,0 @@
1
- import os
2
- import sys
3
- from openpyxl.utils.cell import get_column_letter
4
- import tomllib
5
- from tkinter import filedialog, ttk
6
- import tkinter as tk
7
-
8
- from fnschool import *
9
- from fnschool.canteen.path import *
10
- from fnschool.canteen.food import *
11
- from fnschool.canteen.spreadsheet.base import *
12
- from openpyxl.worksheet.datavalidation import DataValidation
13
-
14
-
15
- class Purchasing(Base):
16
- def __init__(self, bill):
17
- super().__init__(bill)
18
- self.p_path_key = _("parent_path_of_purchasing_file")
19
- self._path = None
20
-
21
- condiment_class_names = ["调味类", "调味品类", "调味"]
22
- self.condiment_class_names = (
23
- condiment_class_names if is_zh_CN else condiment_class_names
24
- )
25
-
26
- self._food_name_col = [
27
- None,
28
- None,
29
- ["商品名称", "食材名称", "食品名称"],
30
- ]
31
- self._unit_name_col = [
32
- None,
33
- None,
34
- ["订货单位", "食材单位", "订购单位", "计量单位"],
35
- ]
36
- self._total_price_col = [
37
- None,
38
- None,
39
- [
40
- "总价",
41
- "折后金额",
42
- "总金额",
43
- "折前金额",
44
- ],
45
- ]
46
- self._xdate_col = [
47
- None,
48
- None,
49
- ["入库日期", "送货日期", "检查日期", "清点日期", "x日期", "日期"],
50
- ]
51
- self._purchaser_col = [
52
- None,
53
- None,
54
- [
55
- "下单单位名",
56
- "客户名称",
57
- "购买者",
58
- "购买者名称",
59
- "顾客名称",
60
- "购入单位名",
61
- ],
62
- ]
63
- self._count_col = [
64
- None,
65
- None,
66
- ["总数", "数量", "发货数量", "记账数量", "总数量", "菜品数量"],
67
- ]
68
- self._meal_type_col = [
69
- None,
70
- None,
71
- ["餐类", "订单类型", "餐型"],
72
- ]
73
- self._abandoned_col = [
74
- None,
75
- None,
76
- [
77
- "不计",
78
- "是不计",
79
- "未入库",
80
- "非入库",
81
- "不需入库",
82
- "是非入库",
83
- ],
84
- ]
85
- self._inventory_col = [
86
- None,
87
- None,
88
- [
89
- "盘存",
90
- "存余",
91
- "结余",
92
- "是结余",
93
- "剩余",
94
- "是剩余",
95
- "是盘存",
96
- ],
97
- ]
98
-
99
- self._food_class_col = [
100
- "食材大类",
101
- None,
102
- ["食材大类", "大类", "食材分类", "食材主类"],
103
- ]
104
- self._wb = None
105
- self._sheet = None
106
- self._headers = None
107
- self.edited_cell_font = Font(color="00FF0000")
108
- self._cols = None
109
- self._food_class_dv = None
110
- self._pd_date = None
111
- self._meal_types = None
112
-
113
- self.essential_cols = [
114
- False,
115
- [
116
- self._food_name_col,
117
- self._unit_name_col,
118
- self._total_price_col,
119
- self._xdate_col,
120
- self._purchaser_col,
121
- self._count_col,
122
- # self._food_class_col,
123
- ],
124
- ]
125
-
126
- @property
127
- def pd_data(self):
128
- if self._pd_date is None:
129
- self._pd_date = pd.read_excel(self.path)
130
- return self._pd_date
131
-
132
- @pd_data.setter
133
- def pd_data(self, pd_data):
134
- self._pd_date = pd_data
135
- pass
136
-
137
- @property
138
- def food_class_dv(self):
139
- if not self._food_class_dv:
140
- self._food_class_dv = DataValidation(
141
- type="list",
142
- formula1=(
143
- '"'
144
- + ",".join(["蔬菜类"] + list(self.food_classes.keys()))
145
- + '"'
146
- ),
147
- )
148
- return self._food_class_dv
149
-
150
- def essential_cols_pass(self):
151
- check_pass, cols = self.essential_cols
152
- if check_pass:
153
- return True
154
- missed_cols = []
155
- for col in cols:
156
- col0 = self.get_optional_col(col)
157
- if not col[1]:
158
- missed_cols.append(col[-1][0])
159
- if len(missed_cols) > 0:
160
- print_error(
161
- _('Column "{0}" was no found.').format("".join(missed_cols))
162
- if len(col) == 1
163
- else _('Columns "{0}" were no found.').format(
164
- _(",").join(missed_cols)
165
- )
166
- )
167
- self.essential_cols[0] = False
168
- return False
169
- self.essential_cols[0] = True
170
- return True
171
-
172
- def get_col(self, col):
173
-
174
- if not self.essential_cols_pass():
175
- exit()
176
-
177
- if not col[1]:
178
- col0 = [
179
- (n, self.headers.index(n) + 1)
180
- for n in self.headers
181
- if n in col[2]
182
- ]
183
-
184
- if len(col0) < 1:
185
- print_error(
186
- _('Column "{0}" was not found. Exit').format(col[2][0])
187
- )
188
- exit()
189
-
190
- col0 = col0[-1]
191
- col[0] = col0[0]
192
- col[1] = col0[1]
193
-
194
- return col
195
-
196
- def get_optional_col(self, col):
197
- if not col[1]:
198
- col0 = [
199
- (n, self.headers.index(n) + 1)
200
- for n in self.headers
201
- if n in col[2]
202
- ]
203
- if len(col0) < 1:
204
- return None
205
- col0 = col0[-1]
206
- col[0] = col0[0]
207
- col[1] = col0[1]
208
-
209
- return col
210
-
211
- @property
212
- def col_indexes(self):
213
- indexes = [c[1] for c in self.cols]
214
- indexes = sorted(indexes)
215
- return indexes
216
-
217
- @property
218
- def cols(self):
219
- if not self._cols:
220
- self._cols = [
221
- self.xdate_col,
222
- self.purchaser_col,
223
- self.food_name_col,
224
- self.food_class_col,
225
- self.unit_name_col,
226
- self.count_col,
227
- self.total_price_col,
228
- self.abandoned_col,
229
- self.inventory_col,
230
- self.meal_type_col,
231
- ]
232
- return self._cols
233
-
234
- @property
235
- def xdate_col(self):
236
- return self.get_col(self._xdate_col)
237
-
238
- @property
239
- def purchaser_col(self):
240
- return self.get_col(self._purchaser_col)
241
-
242
- @property
243
- def meal_type_col(self):
244
- return self.get_optional_col(self._meal_type_col)
245
-
246
- @property
247
- def food_name_col(self):
248
- return self.get_col(self._food_name_col)
249
-
250
- @property
251
- def food_class_col(self):
252
- return self.get_col(self._food_class_col)
253
-
254
- @property
255
- def unit_name_col(self):
256
- return self.get_col(self._unit_name_col)
257
-
258
- @property
259
- def count_col(self):
260
- return self.get_col(self._count_col)
261
-
262
- @property
263
- def total_price_col(self):
264
- return self.get_col(self._total_price_col)
265
-
266
- @property
267
- def abandoned_col(self):
268
- return self.get_col(self._abandoned_col)
269
-
270
- @property
271
- def inventory_col(self):
272
- return self.get_col(self._inventory_col)
273
-
274
- @property
275
- def wb(self):
276
- if not self._wb:
277
- print_info(_('Loading data from "{0}".').format(self.path))
278
- self._wb = load_workbook(self.path)
279
- return self._wb
280
-
281
- @wb.deleter
282
- def wb(self):
283
- self._wb = None
284
- self._sheet = None
285
- self._headers = None
286
-
287
- @property
288
- def sheet(self):
289
- if not self._sheet:
290
- self._sheet = self.wb.active
291
- return self._sheet
292
-
293
- @property
294
- def headers(self):
295
- if not self._headers:
296
- self._headers = [
297
- v
298
- for v in [
299
- self.sheet.cell(1, col_index).value
300
- for col_index in range(1, self.sheet.max_column + 1)
301
- ]
302
- if v
303
- ]
304
- return self._headers
305
-
306
- def add_class_col(self):
307
- if not any(
308
- [fclass in self.headers for fclass in self._food_class_col[2]]
309
- ):
310
- fname_col_index = (
311
- max(
312
- [
313
- self._headers.index(n)
314
- for n in self._headers
315
- if n in self._food_name_col[2]
316
- ]
317
- )
318
- + 1
319
- )
320
- fclass_col_index = fname_col_index + 1
321
- self.sheet.insert_cols(fclass_col_index, 1)
322
- self.sheet.cell(1, fclass_col_index, self._food_class_col[0])
323
- for row_index in range(2, self.sheet.max_row + 1):
324
- fname = self.sheet.cell(row_index, fname_col_index).value
325
- if not fname:
326
- break
327
- cell = self.sheet.cell(row_index, fclass_col_index)
328
- cell.value = self.get_food_class(fname)
329
- cell.font = self.edited_cell_font
330
- self.food_class_dv.add(cell)
331
- self.wb.save(self.path)
332
- self.wb.close()
333
- del self.wb
334
- print_info(
335
- _(
336
- 'Column "{0}" has been updated, '
337
- + "feel free to open new issue if some "
338
- + "food with the wrong class ({1}). "
339
- ).format(self._food_class_col[0], get_new_issue_url())
340
- )
341
- print_warning(
342
- _(
343
- "Ok, I'd like to check and update it. "
344
- + "(Press any key to check the file)"
345
- )
346
- )
347
- get_input()
348
- open_path(self.path)
349
- print_info(
350
- _(
351
- "I have checked it, all classes of "
352
- + "food are right, and I closed the "
353
- + "file. (Press any key to continue)"
354
- )
355
- )
356
- get_input()
357
- pass
358
-
359
- @property
360
- def food_classes(self):
361
- food_classes = self.bill.food_classes
362
- return food_classes
363
-
364
- def food_name_like(self, name, like):
365
- not_likes = None
366
- if "!" in like:
367
- like = like.split("!")
368
- not_likes = like[1:]
369
- like = like[0]
370
-
371
- result = None
372
- like_value = like.replace("*", "")
373
- if like.startswith("*") and not like.endswith("*"):
374
- result = name.endswith(like_value)
375
- elif like.endswith("*") and not like.startswith("*"):
376
- result = name.startswith(like_value)
377
- elif not "*" in like:
378
- result = like_value == name
379
- elif like.startswith("*") and like.endswith("*"):
380
- result = like_value in name
381
-
382
- if not_likes:
383
- result = result and not any(
384
- [self.food_name_like(name, nl) for nl in not_likes]
385
- )
386
- return result
387
- pass
388
-
389
- def get_food_class(self, name):
390
- food_classes = self.food_classes
391
- for fclass, name_likes in food_classes.items():
392
- for name_like in name_likes:
393
- if self.food_name_like(name, name_like):
394
- return fclass
395
- return "蔬菜类"
396
-
397
- @property
398
- def path(self):
399
- p_dpath = self.config.get(self.p_path_key)
400
- initialdir = (
401
- p_dpath
402
- if (p_dpath and Path(p_dpath).exists())
403
- else ((Path.home() / "Downloads").as_posix())
404
- )
405
- if not self._path:
406
- print_info(
407
- _(
408
- "{0} need a purchasing list file, "
409
- + "and it's file type should be '.xlsx'. "
410
- + "The column names of it:"
411
- ).format(app_name)
412
- + _(
413
- ""
414
- + "\n\tcolumn type example"
415
- + "\n\t送货日期 Text 2024-03-01"
416
- + "\n\t食材名称 Text 香菜"
417
- + "\n\t餐类 Text 正餐 (Optional)"
418
- + "\n\t数量 Number 20"
419
- + "\n\t计量单位 Text 斤"
420
- + "\n\t总价 Number 20.0"
421
- + "\n\t购买者 Text "
422
- + "\n\t是不计 Text y"
423
- + "\n\t是结余 Text y"
424
- )
425
- )
426
- print_info(_("Please select a purchasing file."))
427
- filetypes = ((_("Spreadsheet Files"), "*.xlsx"),)
428
-
429
- tkroot = tk.Tk()
430
- tkroot.withdraw()
431
-
432
- filename = filedialog.askopenfilename(
433
- parent=tkroot,
434
- title=_("Please select the purchasing file"),
435
- initialdir=initialdir,
436
- filetypes=filetypes,
437
- )
438
-
439
- if filename is None or filename == ():
440
- print_warning(_("No file was selected, exit."))
441
- exit()
442
- return None
443
- print_info(
444
- _('Purchasing list file "{0}" has been selected.').format(
445
- filename
446
- )
447
- )
448
- self._path = filename
449
- self.config.save(self.p_path_key, Path(self._path).parent.as_posix())
450
- return self._path
451
-
452
- def update_data_validations(self):
453
- if not self.food_class_dv in self.sheet.data_validations:
454
- self.sheet.add_data_validation(self.food_class_dv)
455
-
456
- def update(self):
457
- self.update_data_validations()
458
- self.add_class_col()
459
- self.update_inventories()
460
-
461
- def get_meal_types(self):
462
- data = self.pd_data
463
- col = self.meal_type_col
464
- if not col:
465
- return []
466
- self._meal_types = list(set(data.loc[:, col[0]]))
467
- if len(self._meal_types) > 0:
468
- return self._meal_types
469
- return None
470
-
471
- def update_inventories(self):
472
-
473
- merged_ranges = list(self.sheet.merged_cells.ranges)
474
- for cell_group in merged_ranges:
475
- sheet.unmerge_cells(str(cell_group))
476
-
477
- inventories_len = len(
478
- [
479
- row_index
480
- for row_index in range(2, self.sheet.max_row + 1)
481
- if self.sheet.cell(row_index, self.inventory_col[1]).value
482
- ]
483
- )
484
-
485
- if inventories_len > 0:
486
- return
487
-
488
- print_warning(
489
- _("The remaining food wasn't read " + 'from "{0}".').format(
490
- self.path,
491
- )
492
- )
493
-
494
- meal_types = self.get_meal_types()
495
- inventory = self.bill.spreadsheet.inventory
496
-
497
- update_yn = False
498
-
499
- for meal_type in meal_types:
500
- bill_fpath = self.operator.get_bill_fpath(meal_type)
501
- print_info(
502
- _(
503
- "{0} is reading remaining foods from "
504
- + 'Sheet "{1}" of Spreadsheet "{2}" ......'
505
- ).format(
506
- app_name,
507
- inventory.sheet_name,
508
- bill_fpath,
509
- )
510
- )
511
- saved_ifoods = inventory.get_save_foods(meal_type)
512
- saved_ifoods_len = len(saved_ifoods)
513
- if saved_ifoods_len < 1:
514
- print_warning(
515
- _('There is no saved inventories from "{0}"').format(
516
- bill_fpath
517
- )
518
- )
519
- return
520
-
521
- print_warning(
522
- (
523
- _(
524
- "Some remaining foods have been read "
525
- + 'from sheet "{0}" of spreadsheet "{1}":'
526
- )
527
- if len(saved_ifoods) > 1
528
- else _(
529
- "The remaining food has been read"
530
- + 'from sheet "{0}" of spreadsheet "{1}":'
531
- )
532
- ).format(inventory.sheet_name, bill_fpath)
533
- )
534
-
535
- saved_ifoods_len2 = len(str(saved_ifoods_len))
536
- saved_ifoods_s = sqr_slist(
537
- [
538
- (
539
- f"({i+1:>{saved_ifoods_len2}}) "
540
- + f"{f0.name}:{f0.count} {f0.unit_name}"
541
- + f"\u2a09 {f0.unit_price:.2f} "
542
- + f"{self.bill.currency.unit}/"
543
- + f"{f0.unit_name}={f0.total_price:.2f} "
544
- + f"{self.bill.currency.unit}"
545
- )
546
- for i, f0 in enumerate(saved_ifoods)
547
- ]
548
- )
549
-
550
- saved_ifoods_s_len = max(
551
- [len(s) for s in saved_ifoods_s.split("\n")]
552
- )
553
- saved_ifoods_info = (
554
- _("Purchaser: ")
555
- + saved_ifoods[0].purchaser
556
- + "\n"
557
- + _("Inventory data: ")
558
- + saved_ifoods[0].xdate.strftime("%Y.%m.%d")
559
- + "\n"
560
- )
561
- print_info(saved_ifoods_s)
562
- print_warning(saved_ifoods_info)
563
- print_warning(
564
- (
565
- _('Fill them in "{0}"? (YyNn, default: "No")')
566
- if len(saved_ifoods) > 1
567
- else _('Fill it in "{0}"? (YyNn, default: "No")')
568
- ).format(self.path)
569
- )
570
-
571
- f_input = get_input().replace(" ", "")
572
- if len(f_input) > 0 and f_input in "Yy":
573
- update_yn = True
574
- max_row = len(
575
- [
576
- row_index
577
- for row_index in range(1, self.sheet.max_row + 1)
578
- if self.sheet.cell(row_index, 1).value
579
- ]
580
- )
581
- for row_index in range(
582
- max_row + 1, max_row + 1 + len(saved_ifoods)
583
- ):
584
- f = saved_ifoods[row_index - max_row - 1]
585
- self.sheet.cell(
586
- row_index, self.xdate_col[1]
587
- ).number_format = numbers.FORMAT_TEXT
588
- self.sheet.cell(
589
- row_index,
590
- self.xdate_col[1],
591
- f.xdate.strftime("%Y-%m-%d"),
592
- )
593
- self.sheet.cell(
594
- row_index, self.purchaser_col[1], f.purchaser
595
- )
596
- self.sheet.cell(row_index, self.food_name_col[1], f.name)
597
- self.sheet.cell(row_index, self.food_class_col[1], f.fclass)
598
- self.food_class_dv.add(
599
- self.sheet.cell(row_index, self.food_class_col[1])
600
- )
601
- self.sheet.cell(
602
- row_index,
603
- self.unit_name_col[1],
604
- f.unit_name,
605
- )
606
- self.sheet.cell(
607
- row_index, self.count_col[1]
608
- ).number_format = numbers.FORMAT_NUMBER_00
609
- self.sheet.cell(row_index, self.count_col[1], f.count)
610
- self.sheet.cell(
611
- row_index, self.total_price_col[1]
612
- ).number_format = numbers.FORMAT_NUMBER_00
613
- self.sheet.cell(
614
- row_index,
615
- self.total_price_col[1],
616
- f.total_price,
617
- )
618
- self.sheet.cell(row_index, self.inventory_col[1], "y")
619
-
620
- for col_index in self.col_indexes:
621
- if not col_index:
622
- continue
623
- self.sheet.cell(row_index, col_index).font = (
624
- self.edited_cell_font
625
- )
626
-
627
- print_info(
628
- (
629
- _("The remaining foods have been " + 'added to "{0}".')
630
- if len(saved_ifoods) > 1
631
- else _('The remaining food has been added to "{0}".')
632
- ).format(self.path)
633
- )
634
-
635
- pass
636
-
637
- self.wb.save(self.path)
638
- self.wb.close()
639
- del self.wb
640
-
641
- if update_yn:
642
- print_info(
643
- (
644
- _(
645
- "Please check/modify the updated data. "
646
- + "(Press any key to open the file)"
647
- )
648
- )
649
- )
650
- get_input()
651
- open_path(self.path)
652
- print_info(
653
- _("Ok, I checked it, it's ok. " + "(Press any key to continue)")
654
- )
655
- get_input()
656
- pass
657
-
658
- pass
659
-
660
- def split_foods(self):
661
- foods_cp = self.bill.foods.copy()
662
- foods = self.bill.foods
663
-
664
- bad_total_price_foods = []
665
- for f in foods:
666
- total_price_s = str(f.total_price)
667
- if "." in total_price_s and len(total_price_s.split(".")[1]) > 2:
668
- bad_total_price_foods.append(f)
669
- if len(bad_total_price_foods) > 0:
670
- bad_total_price_foods_len = len(bad_total_price_foods)
671
- print_error(
672
- (
673
- _(
674
- "The total price of the following food has more than "
675
- + "two decimal places, and {0} cannot process it."
676
- )
677
- if bad_total_price_foods_len == 1
678
- else _(
679
- "The total price of the following foods have more than "
680
- + "two decimal places, and {0} cannot process them."
681
- )
682
- ).format(app_name)
683
- )
684
- bad_total_price_foods_len2 = len(str(bad_total_price_foods_len + 1))
685
- print_error(
686
- sqr_slist(
687
- [
688
- (
689
- f"{i+1:>{bad_total_price_foods_len2}} "
690
- + _("{0}({1})").format(
691
- f.name,
692
- (
693
- f"{f.xdate.year}.{f.xdate.month:0>2}"
694
- + f".{f.xdate.day:0>2}"
695
- ),
696
- )
697
- + f" {f.total_price}"
698
- )
699
- for i, f in enumerate(bad_total_price_foods)
700
- ]
701
- )
702
- )
703
- print_error(_("Exit."))
704
- exit()
705
-
706
- split_mode = ""
707
-
708
- for i, f in enumerate(foods_cp):
709
- up0, up1, threshold = f.count_threshold
710
- f_count = f.count
711
- f_total_price = f.total_price
712
- f_unit_price = f.unit_price
713
- if threshold:
714
- if split_mode in "YyNn":
715
- print_info(
716
- _(
717
- 'The unit price of "{0}" is an '
718
- + 'infinite decimal, split "{0}"?'
719
- + '(Yes: "Y","y","".'
720
- + ' Yes for rest: "A","a".'
721
- + ' No: "N","n".'
722
- + ' No for rest: "S","s".'
723
- + ' Default: Yes for rest, "A/a".'
724
- + ")"
725
- ).format(f.name)
726
- )
727
- split_mode = get_input()
728
-
729
- if split_mode and split_mode in "Ss":
730
- return
731
-
732
- if split_mode == "":
733
- split_mode = "A"
734
-
735
- if split_mode in "YyAa":
736
- times_char = "\u2a09"
737
- f0 = foods[i]
738
- f0.count = threshold
739
- f0.total_price = up1 * threshold
740
- f0._count_threshold = None
741
-
742
- f1_count = f_count - threshold
743
- f1 = Food(
744
- self.bill,
745
- name=f0.name,
746
- unit_name=f0.unit_name,
747
- count=f1_count,
748
- total_price=f1_count * up0,
749
- xdate=f0.xdate,
750
- purchaser=f0.purchaser,
751
- fclass=f0.fclass,
752
- is_inventory=f0.is_inventory,
753
- is_abandoned=f0.is_abandoned,
754
- meal_type=f0.meal_type,
755
- )
756
-
757
- total_price0 = round(
758
- up1 * threshold + f1_count * up0, self.sd + 1
759
- )
760
- print_info(
761
- _('"{0}" was split:').format(f0.name)
762
- + f"\n\t{f0.unit_price} "
763
- + f"{self.bill.currency.unit}"
764
- + f"/{f0.unit_name} "
765
- + f"{times_char} {f0.count} "
766
- + f"{f0.unit_name} + "
767
- + f"{f1.unit_price} "
768
- + f"{self.bill.currency.unit}"
769
- + f"/{f1.unit_name} "
770
- + f"{times_char} {f1.count} "
771
- + f"{f1.unit_name} = "
772
- + f"{total_price0} "
773
- + f"{self.bill.currency.unit} = "
774
- + f"{f_unit_price} "
775
- + f"{self.bill.currency.unit}"
776
- + f"/{f.unit_name} "
777
- + f"{times_char} {f_count} "
778
- + f"{f.unit_name} = "
779
- + f"{f_total_price}"
780
- + f"{self.bill.currency.unit}"
781
- )
782
-
783
- foods.append(f1)
784
- return
785
-
786
- def abandoned_foods_pass(self, foods):
787
- ab_foods = [
788
- f
789
- for f in foods
790
- if f.is_abandoned and not f.fclass in self.condiment_class_names
791
- ]
792
- if len(ab_foods) < 1:
793
- return True
794
- print_warning(
795
- (
796
- _(
797
- "Normally, {0} only considers non-warehoused foods as "
798
- + "seasonings, but {0} has found that the following food "
799
- + "is non-warehoused, but it is not seasonings:"
800
- )
801
- if len(ab_foods) == 1
802
- else _(
803
- "Normally, {0} only considers non-warehoused foods as "
804
- + "seasonings, but {0} has found that the following foods "
805
- + "are non-warehoused, but they are not seasonings:"
806
- )
807
- ).format(app_name)
808
- )
809
-
810
- print_info(
811
- sqr_slist([_("{0}({1})").format(f.name, f.xdate) for f in ab_foods])
812
- )
813
- print_error(
814
- (
815
- _(
816
- "You may need to modify its food class, "
817
- + "otherwise {0} will go wrong."
818
- )
819
- if len(ab_foods) == 1
820
- else _(
821
- "You may need to modify their food classes, "
822
- + "otherwise {0} will go wrong."
823
- )
824
- ).format(app_name)
825
- + _(
826
- ' Do you want to re-edit "{0}"? '
827
- + "(Yes: 'Y' or 'y'. Default: No.)"
828
- ).format(self.path)
829
- )
830
- re_edit_yn = get_input()
831
- if re_edit_yn and re_edit_yn in "Yy":
832
- open_path(self.path)
833
- print_info(
834
- _(
835
- "Ok, I have re-edited and closed it? "
836
- + "(Press any key to continue)"
837
- )
838
- )
839
- get_input()
840
- return False
841
-
842
- return True
843
-
844
- pass
845
-
846
- def read_pfoods(self):
847
- self.update()
848
- foods = self.pd_data
849
- # if not self.abandoned_foods_pass(foods):
850
- # self.pd_data = None
851
- # return self.read_pfoods()
852
- _foods = []
853
- for __, food in foods.iterrows():
854
- _food = Food(
855
- self.bill,
856
- name=food[self.food_name_col[0]],
857
- unit_name=food[self.unit_name_col[0]],
858
- count=food[self.count_col[0]],
859
- total_price=food[self.total_price_col[0]],
860
- xdate=food[self.xdate_col[0]],
861
- purchaser=food[self.purchaser_col[0]],
862
- fclass=food[self.food_class_col[0]],
863
- )
864
- if self.abandoned_col[0]:
865
- _food.is_abandoned = not pd.isna(food[self.abandoned_col[0]])
866
- if self.inventory_col[0]:
867
- _food.is_inventory = not pd.isna(food[self.inventory_col[0]])
868
- if self.meal_type_col and self.meal_type_col[0]:
869
- _food.meal_type = food[self.meal_type_col[0]]
870
-
871
- _foods.append(_food)
872
-
873
- foods = _foods
874
- foods = sorted(foods, key=lambda f: f.xdate)
875
- self.bill.foods = foods
876
-
877
- self.split_foods()
878
-
879
- self.spreadsheet.preconsuming.pre_consume_foods()
880
-
881
- return foods
882
- pass
883
-
884
-
885
- # The end.