fnschool 20250109.80500.803__py3-none-any.whl → 20251011.80531.840__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 (276) hide show
  1. fnschoo1/__init__.py +52 -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/js/bootstrap.bundle.min.js +4223 -0
  71. fnschoo1/static/js/bootstrap.min.js +2919 -0
  72. fnschoo1/static/js/fnschool.js +84 -0
  73. fnschoo1/static/js/jquery.min.js +5413 -0
  74. fnschoo1/static/js/jquery.slim.min.js +4331 -0
  75. fnschoo1/static/js/popper.min.js +1306 -0
  76. fnschoo1/static_collected/admin/css/autocomplete.css +377 -0
  77. fnschoo1/static_collected/admin/css/base.css +1224 -0
  78. fnschoo1/static_collected/admin/css/changelists.css +334 -0
  79. fnschoo1/static_collected/admin/css/dark_mode.css +136 -0
  80. fnschoo1/static_collected/admin/css/dashboard.css +30 -0
  81. fnschoo1/static_collected/admin/css/forms.css +546 -0
  82. fnschoo1/static_collected/admin/css/login.css +62 -0
  83. fnschoo1/static_collected/admin/css/nav_sidebar.css +145 -0
  84. fnschoo1/static_collected/admin/css/responsive.css +1043 -0
  85. fnschoo1/static_collected/admin/css/responsive_rtl.css +84 -0
  86. fnschoo1/static_collected/admin/css/rtl.css +311 -0
  87. fnschoo1/static_collected/admin/css/vendor/select2/select2.css +708 -0
  88. fnschoo1/static_collected/admin/css/vendor/select2/select2.min.css +1 -0
  89. fnschoo1/static_collected/admin/css/widgets.css +639 -0
  90. fnschoo1/static_collected/admin/js/SelectBox.js +128 -0
  91. fnschoo1/static_collected/admin/js/SelectFilter2.js +503 -0
  92. fnschoo1/static_collected/admin/js/actions.js +232 -0
  93. fnschoo1/static_collected/admin/js/admin/DateTimeShortcuts.js +496 -0
  94. fnschoo1/static_collected/admin/js/admin/RelatedObjectLookups.js +276 -0
  95. fnschoo1/static_collected/admin/js/autocomplete.js +33 -0
  96. fnschoo1/static_collected/admin/js/calendar.js +251 -0
  97. fnschoo1/static_collected/admin/js/cancel.js +29 -0
  98. fnschoo1/static_collected/admin/js/change_form.js +21 -0
  99. fnschoo1/static_collected/admin/js/collapse.js +43 -0
  100. fnschoo1/static_collected/admin/js/core.js +174 -0
  101. fnschoo1/static_collected/admin/js/filters.js +37 -0
  102. fnschoo1/static_collected/admin/js/inlines.js +439 -0
  103. fnschoo1/static_collected/admin/js/jquery.init.js +8 -0
  104. fnschoo1/static_collected/admin/js/nav_sidebar.js +81 -0
  105. fnschoo1/static_collected/admin/js/popup_response.js +24 -0
  106. fnschoo1/static_collected/admin/js/prepopulate.js +43 -0
  107. fnschoo1/static_collected/admin/js/prepopulate_init.js +20 -0
  108. fnschoo1/static_collected/admin/js/theme.js +57 -0
  109. fnschoo1/static_collected/admin/js/urlify.js +529 -0
  110. fnschoo1/static_collected/admin/js/vendor/jquery/jquery.js +10913 -0
  111. fnschoo1/static_collected/admin/js/vendor/jquery/jquery.min.js +2 -0
  112. fnschoo1/static_collected/admin/js/vendor/select2/i18n/af.js +43 -0
  113. fnschoo1/static_collected/admin/js/vendor/select2/i18n/ar.js +36 -0
  114. fnschoo1/static_collected/admin/js/vendor/select2/i18n/az.js +33 -0
  115. fnschoo1/static_collected/admin/js/vendor/select2/i18n/bg.js +38 -0
  116. fnschoo1/static_collected/admin/js/vendor/select2/i18n/bn.js +39 -0
  117. fnschoo1/static_collected/admin/js/vendor/select2/i18n/bs.js +48 -0
  118. fnschoo1/static_collected/admin/js/vendor/select2/i18n/ca.js +41 -0
  119. fnschoo1/static_collected/admin/js/vendor/select2/i18n/cs.js +62 -0
  120. fnschoo1/static_collected/admin/js/vendor/select2/i18n/da.js +37 -0
  121. fnschoo1/static_collected/admin/js/vendor/select2/i18n/de.js +41 -0
  122. fnschoo1/static_collected/admin/js/vendor/select2/i18n/dsb.js +51 -0
  123. fnschoo1/static_collected/admin/js/vendor/select2/i18n/el.js +43 -0
  124. fnschoo1/static_collected/admin/js/vendor/select2/i18n/en.js +41 -0
  125. fnschoo1/static_collected/admin/js/vendor/select2/i18n/es.js +41 -0
  126. fnschoo1/static_collected/admin/js/vendor/select2/i18n/et.js +38 -0
  127. fnschoo1/static_collected/admin/js/vendor/select2/i18n/eu.js +45 -0
  128. fnschoo1/static_collected/admin/js/vendor/select2/i18n/fa.js +42 -0
  129. fnschoo1/static_collected/admin/js/vendor/select2/i18n/fi.js +42 -0
  130. fnschoo1/static_collected/admin/js/vendor/select2/i18n/fr.js +43 -0
  131. fnschoo1/static_collected/admin/js/vendor/select2/i18n/gl.js +40 -0
  132. fnschoo1/static_collected/admin/js/vendor/select2/i18n/he.js +44 -0
  133. fnschoo1/static_collected/admin/js/vendor/select2/i18n/hi.js +40 -0
  134. fnschoo1/static_collected/admin/js/vendor/select2/i18n/hr.js +45 -0
  135. fnschoo1/static_collected/admin/js/vendor/select2/i18n/hsb.js +51 -0
  136. fnschoo1/static_collected/admin/js/vendor/select2/i18n/hu.js +44 -0
  137. fnschoo1/static_collected/admin/js/vendor/select2/i18n/hy.js +40 -0
  138. fnschoo1/static_collected/admin/js/vendor/select2/i18n/id.js +36 -0
  139. fnschoo1/static_collected/admin/js/vendor/select2/i18n/is.js +37 -0
  140. fnschoo1/static_collected/admin/js/vendor/select2/i18n/it.js +43 -0
  141. fnschoo1/static_collected/admin/js/vendor/select2/i18n/ja.js +40 -0
  142. fnschoo1/static_collected/admin/js/vendor/select2/i18n/ka.js +42 -0
  143. fnschoo1/static_collected/admin/js/vendor/select2/i18n/km.js +38 -0
  144. fnschoo1/static_collected/admin/js/vendor/select2/i18n/ko.js +42 -0
  145. fnschoo1/static_collected/admin/js/vendor/select2/i18n/lt.js +45 -0
  146. fnschoo1/static_collected/admin/js/vendor/select2/i18n/lv.js +41 -0
  147. fnschoo1/static_collected/admin/js/vendor/select2/i18n/mk.js +42 -0
  148. fnschoo1/static_collected/admin/js/vendor/select2/i18n/ms.js +38 -0
  149. fnschoo1/static_collected/admin/js/vendor/select2/i18n/nb.js +38 -0
  150. fnschoo1/static_collected/admin/js/vendor/select2/i18n/ne.js +44 -0
  151. fnschoo1/static_collected/admin/js/vendor/select2/i18n/nl.js +46 -0
  152. fnschoo1/static_collected/admin/js/vendor/select2/i18n/pl.js +43 -0
  153. fnschoo1/static_collected/admin/js/vendor/select2/i18n/ps.js +41 -0
  154. fnschoo1/static_collected/admin/js/vendor/select2/i18n/pt-BR.js +39 -0
  155. fnschoo1/static_collected/admin/js/vendor/select2/i18n/pt.js +41 -0
  156. fnschoo1/static_collected/admin/js/vendor/select2/i18n/ro.js +43 -0
  157. fnschoo1/static_collected/admin/js/vendor/select2/i18n/ru.js +48 -0
  158. fnschoo1/static_collected/admin/js/vendor/select2/i18n/sk.js +61 -0
  159. fnschoo1/static_collected/admin/js/vendor/select2/i18n/sl.js +41 -0
  160. fnschoo1/static_collected/admin/js/vendor/select2/i18n/sq.js +43 -0
  161. fnschoo1/static_collected/admin/js/vendor/select2/i18n/sr-Cyrl.js +48 -0
  162. fnschoo1/static_collected/admin/js/vendor/select2/i18n/sr.js +48 -0
  163. fnschoo1/static_collected/admin/js/vendor/select2/i18n/sv.js +40 -0
  164. fnschoo1/static_collected/admin/js/vendor/select2/i18n/th.js +36 -0
  165. fnschoo1/static_collected/admin/js/vendor/select2/i18n/tk.js +36 -0
  166. fnschoo1/static_collected/admin/js/vendor/select2/i18n/tr.js +40 -0
  167. fnschoo1/static_collected/admin/js/vendor/select2/i18n/uk.js +59 -0
  168. fnschoo1/static_collected/admin/js/vendor/select2/i18n/vi.js +37 -0
  169. fnschoo1/static_collected/admin/js/vendor/select2/i18n/zh-CN.js +36 -0
  170. fnschoo1/static_collected/admin/js/vendor/select2/i18n/zh-TW.js +33 -0
  171. fnschoo1/static_collected/admin/js/vendor/select2/select2.full.js +7115 -0
  172. fnschoo1/static_collected/admin/js/vendor/select2/select2.full.min.js +2 -0
  173. fnschoo1/static_collected/admin/js/vendor/xregexp/xregexp.js +4993 -0
  174. fnschoo1/static_collected/admin/js/vendor/xregexp/xregexp.min.js +160 -0
  175. fnschoo1/static_collected/css/bootstrap.min.css +11776 -0
  176. fnschoo1/static_collected/css/fnschool.css +26 -0
  177. fnschoo1/static_collected/js/bootstrap.bundle.min.js +4223 -0
  178. fnschoo1/static_collected/js/bootstrap.min.js +2919 -0
  179. fnschoo1/static_collected/js/fnschool.js +84 -0
  180. fnschoo1/static_collected/js/jquery.min.js +5413 -0
  181. fnschoo1/static_collected/js/jquery.slim.min.js +4331 -0
  182. fnschoo1/static_collected/js/popper.min.js +1306 -0
  183. fnschoo1/templates/base/_css.html +1 -0
  184. fnschoo1/templates/base/_js.html +15 -0
  185. fnschoo1/templates/base/content.html +30 -0
  186. fnschoo1/templates/base/header_content_footer.html +35 -0
  187. fnschoo1/templates/close.html +11 -0
  188. fnschoo1/templates/home.html +51 -0
  189. fnschoo1/templates/includes/_footer.html +39 -0
  190. fnschoo1/templates/includes/_header.html +77 -0
  191. fnschoo1/templates/includes/_navigation.html +0 -0
  192. fnschoo1/templates/includes/_paginator.html +27 -0
  193. fnschoo1/templates/registration/logged_out.html +0 -0
  194. fnschoo1/templates/registration/login.html +0 -0
  195. fnschool-20251011.80531.840.dist-info/METADATA +179 -0
  196. fnschool-20251011.80531.840.dist-info/RECORD +207 -0
  197. {fnschool-20250109.80500.803.dist-info → fnschool-20251011.80531.840.dist-info}/WHEEL +1 -1
  198. fnschool-20251011.80531.840.dist-info/entry_points.txt +2 -0
  199. fnschool-20251011.80531.840.dist-info/top_level.txt +1 -0
  200. fnschool/__init__.py +0 -35
  201. fnschool/__main__.py +0 -16
  202. fnschool/app.py +0 -103
  203. fnschool/canteen/__init__.py +0 -3
  204. fnschool/canteen/__main__.py +0 -3
  205. fnschool/canteen/bill.py +0 -253
  206. fnschool/canteen/canteen.py +0 -1
  207. fnschool/canteen/canteen.toml +0 -61
  208. fnschool/canteen/config.py +0 -10
  209. fnschool/canteen/consuming.py +0 -53
  210. fnschool/canteen/currency.py +0 -17
  211. fnschool/canteen/data/bill.i18n.xlsx +0 -0
  212. fnschool/canteen/data/bill.xlsx +0 -0
  213. fnschool/canteen/data/consuming.xlsx +0 -0
  214. fnschool/canteen/data/purchase_list.xlsx +0 -0
  215. fnschool/canteen/entry.py +0 -40
  216. fnschool/canteen/food.py +0 -206
  217. fnschool/canteen/food_classes.py +0 -33
  218. fnschool/canteen/food_classes.toml +0 -64
  219. fnschool/canteen/operator.py +0 -91
  220. fnschool/canteen/path.py +0 -28
  221. fnschool/canteen/spreadsheet/base.py +0 -212
  222. fnschool/canteen/spreadsheet/consuming.py +0 -310
  223. fnschool/canteen/spreadsheet/consumingsum.py +0 -76
  224. fnschool/canteen/spreadsheet/cover.py +0 -64
  225. fnschool/canteen/spreadsheet/ctspreadsheet.py +0 -351
  226. fnschool/canteen/spreadsheet/food.py +0 -680
  227. fnschool/canteen/spreadsheet/inventory.py +0 -375
  228. fnschool/canteen/spreadsheet/merging.py +0 -340
  229. fnschool/canteen/spreadsheet/preconsuming.py +0 -329
  230. fnschool/canteen/spreadsheet/purchasing.py +0 -885
  231. fnschool/canteen/spreadsheet/purchasingsum.py +0 -110
  232. fnschool/canteen/spreadsheet/spreadsheet.py +0 -363
  233. fnschool/canteen/spreadsheet/translating.py +0 -12
  234. fnschool/canteen/spreadsheet/unwarehousing.py +0 -178
  235. fnschool/canteen/spreadsheet/unwarehousingsum.py +0 -11
  236. fnschool/canteen/spreadsheet/warehousing.py +0 -360
  237. fnschool/canteen/spreadsheet/workbook.py +0 -17
  238. fnschool/canteen/test.py +0 -97
  239. fnschool/config.py +0 -48
  240. fnschool/entry.py +0 -67
  241. fnschool/exam/__init__.py +0 -8
  242. fnschool/exam/data/parental_emails.xlsx +0 -0
  243. fnschool/exam/data/score.xlsx +0 -0
  244. fnschool/exam/email.py +0 -349
  245. fnschool/exam/entry.py +0 -36
  246. fnschool/exam/language.py +0 -19
  247. fnschool/exam/path.py +0 -24
  248. fnschool/exam/score.py +0 -1191
  249. fnschool/exam/subject.py +0 -20
  250. fnschool/exam/teacher.py +0 -54
  251. fnschool/external.py +0 -89
  252. fnschool/games/__init__.py +0 -1
  253. fnschool/games/__main__.py +0 -1
  254. fnschool/games/games.py +0 -1
  255. fnschool/inoutput.py +0 -97
  256. fnschool/language.py +0 -40
  257. fnschool/locales/en_US/LC_MESSAGES/fnschool.mo +0 -0
  258. fnschool/locales/zh_CN/LC_MESSAGES/fnschool.mo +0 -0
  259. fnschool/locales/zh_HK/LC_MESSAGES/fnschool.mo +0 -0
  260. fnschool/locales/zh_SG/LC_MESSAGES/fnschool.mo +0 -0
  261. fnschool/locales/zh_TW/LC_MESSAGES/fnschool.mo +0 -0
  262. fnschool/path.py +0 -45
  263. fnschool/test.py +0 -24
  264. fnschool/user.py +0 -262
  265. fnschool-20250109.80500.803.dist-info/METADATA +0 -342
  266. fnschool-20250109.80500.803.dist-info/RECORD +0 -78
  267. fnschool-20250109.80500.803.dist-info/entry_points.txt +0 -5
  268. fnschool-20250109.80500.803.dist-info/top_level.txt +0 -1
  269. /fnschool/canteen/consume.py → /fnschoo1/canteen/__init__.py +0 -0
  270. /fnschool/canteen/food_recount.toml → /fnschoo1/canteen/migrations/__init__.py +0 -0
  271. {fnschool/canteen/spreadsheet → fnschoo1/canteen/workbook}/__init__.py +0 -0
  272. /fnschool/exam/__main__.py → /fnschoo1/fnschool/templatetags/__init__.py +0 -0
  273. /fnschool/canteen/food_recounts.toml → /fnschoo1/profiles/__init__.py +0 -0
  274. /fnschool/canteen/warehouse.py → /fnschoo1/profiles/migrations/__init__.py +0 -0
  275. /fnschool/canteen/workbook.toml → /fnschoo1/templates/base/_content.html +0 -0
  276. {fnschool-20250109.80500.803.dist-info → fnschool-20251011.80531.840.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,2098 @@
1
+ import calendar
2
+ import io
3
+ import math
4
+ import os
5
+ import random
6
+ import re
7
+ import zipfile
8
+ from datetime import date, datetime, timedelta
9
+ from decimal import ROUND_HALF_UP, Decimal
10
+ from pathlib import Path
11
+
12
+ import numpy as np
13
+ import pandas as pd
14
+ from dateutil.relativedelta import relativedelta
15
+ from django.contrib.auth.decorators import login_required
16
+ from django.contrib.auth.mixins import LoginRequiredMixin
17
+ from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
18
+ from django.db.models import DecimalField, ExpressionWrapper, F, Q, Sum, Value
19
+ from django.db.models.functions import Coalesce
20
+ from django.http import HttpResponse
21
+ from django.shortcuts import get_object_or_404, redirect, render
22
+ from django.urls import reverse_lazy
23
+ from django.utils import translation
24
+ from django.utils.encoding import escape_uri_path
25
+ from django.utils.translation import gettext as _
26
+ from django.views.decorators.http import require_POST
27
+ from django.views.generic import (
28
+ CreateView,
29
+ DeleteView,
30
+ DetailView,
31
+ ListView,
32
+ UpdateView,
33
+ )
34
+ from fnschool import count_chinese_characters
35
+ from openpyxl import Workbook
36
+ from openpyxl.comments import Comment
37
+ from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
38
+ from openpyxl.utils import get_column_letter
39
+
40
+ from ..forms import (
41
+ CategoryForm,
42
+ ConsumptionForm,
43
+ IngredientForm,
44
+ PurchasedIngredientsWorkBookForm,
45
+ )
46
+ from ..models import Category, Consumption, Ingredient, MealType
47
+ from ..views import decimal_prec
48
+
49
+
50
+ def get_CNY_TEXT(amount):
51
+ units = {
52
+ "0": "\u96f6", # ling2
53
+ "1": "\u58f9", # yi1
54
+ "2": "\u8d30", # er4
55
+ "3": "\u53c1", # san1
56
+ "4": "\u8086", # si4
57
+ "5": "\u4f0d", # wu3
58
+ "6": "\u9646", # liu4
59
+ "7": "\u67d2", # qi1
60
+ "8": "\u634c", # ba1
61
+ "9": "\u7396", # jiu3
62
+ }
63
+
64
+ levels = [
65
+ "",
66
+ "\u62fe", # shi2
67
+ "\u4f70", # bai3
68
+ "\u4edf", # qian1
69
+ "\u4e07", # wan4
70
+ "\u4ebf", # yi4
71
+ "\u5143", # yuan2
72
+ "\u89d2", # jiao3
73
+ "\u5206", # fen1
74
+ "\u6574", # zheng3
75
+ ]
76
+
77
+ is_negative = False
78
+ if amount < 0:
79
+ is_negative = True
80
+ amount = abs(amount)
81
+ if amount == 0:
82
+ return "\u96f6\u5143\u6574" # ling2 yuan2 zheng3.
83
+
84
+ amount = Decimal(str(amount)).quantize(
85
+ Decimal("0.00"), rounding=ROUND_HALF_UP
86
+ )
87
+ amount_str = str(amount)
88
+
89
+ integer_part = None
90
+ decimal_part = None
91
+ if "." in amount_str:
92
+ integer_part, decimal_part = amount_str.split(".")
93
+ else:
94
+ integer_part = amount_str
95
+ decimal_part = "00"
96
+
97
+ result = []
98
+ integer_part = integer_part.zfill(16)
99
+
100
+ groups = [
101
+ integer_part[-16:-12],
102
+ integer_part[-12:-8],
103
+ integer_part[-8:-4],
104
+ integer_part[-4:],
105
+ ]
106
+
107
+ group_names = [
108
+ "\u4e07", # wan4
109
+ "\u4ebf", # yi4
110
+ "\u4e07", # wan4
111
+ "\u5143", # yuan2
112
+ ]
113
+
114
+ for i, group in enumerate(groups):
115
+ group = group.lstrip("0")
116
+ if not group:
117
+ continue
118
+
119
+ for j, digit in enumerate(group):
120
+ if digit == "0":
121
+ if result and result[-1] != "\u96f6": # \\u96f6 is ling2 .
122
+ result.append("\u96f6") # \\u96f6 is ling2 .
123
+ else:
124
+ result.append(units[digit])
125
+
126
+ if len(group) - j - 1 > 0:
127
+ result.append(levels[len(group) - j - 1])
128
+
129
+ if group_names[i]:
130
+ result.append(group_names[i])
131
+
132
+ if decimal_part != "00":
133
+ if decimal_part[0] != "0":
134
+ result.append(units[decimal_part[0]])
135
+ result.append("\u89d2") # \\u89d2 is jiao3 .
136
+
137
+ if decimal_part[1] != "0":
138
+ result.append(units[decimal_part[1]])
139
+ result.append("\u5206") # \\u5206 is fen1 .
140
+ else:
141
+ result.append("\u6574") # \\u6574 is zheng3 .
142
+
143
+ output = "".join(result)
144
+
145
+ output = re.sub("\u96f6+", "\u96f6", output)
146
+ output = re.sub("\u96f6([\u4e07\u4ebf])", r"\1", output)
147
+ output = re.sub("\u96f6\u5143", "\u5143", output)
148
+ output = re.sub("\u96f6\u89d2\u96f6\u5206", "", output)
149
+ output = re.sub("\u96f6\u5206", "", output)
150
+
151
+ if output.startswith("\u58f9\u62fe"):
152
+ output = output.replace("\u58f9\u62fe", "\u62fe", 1)
153
+
154
+ if is_negative:
155
+ output = "\u8d1f" + output
156
+
157
+ return output
158
+
159
+
160
+ def is_zh_CN():
161
+ lang = translation.get_language()
162
+ return lang.lower() in ["zh-cn", "zh-hans"]
163
+
164
+
165
+ def set_column_width_in_inches(worksheet, column, inches):
166
+ char_width = inches * 96 / 7
167
+
168
+ if isinstance(column, int):
169
+ col_letter = get_column_letter(column)
170
+ else:
171
+ col_letter = column
172
+ worksheet.column_dimensions[col_letter].width = char_width
173
+
174
+
175
+ def set_row_height_in_inches(worksheet, row, inches):
176
+ points = inches * 72
177
+ worksheet.row_dimensions[row].height = points
178
+
179
+
180
+ class CanteenWorkBook:
181
+ def __init__(self, request, month, meal_type):
182
+ self.wb = Workbook()
183
+ self.wb[self.wb.sheetnames[0]].sheet_state = "hidden"
184
+ self.cover_sheet = self.wb.create_sheet(title=_("Sheet Cover"))
185
+ self.storage_sheet = self.wb.create_sheet(title=_("Sheet Storage"))
186
+ self.storage_list_sheet = self.wb.create_sheet(
187
+ title=_("Sheet Storage List")
188
+ )
189
+ self.non_storage_sheet = self.wb.create_sheet(
190
+ title=_("Sheet Non-Storage")
191
+ )
192
+ self.non_storage_list_sheet = self.wb.create_sheet(
193
+ title=_("Sheet Non-Storage List")
194
+ )
195
+ self.consumption_sheet = self.wb.create_sheet(
196
+ title=_("Sheet Consumption")
197
+ )
198
+ self.consumption_list_sheet = self.wb.create_sheet(
199
+ title=_("Sheet Consumption List")
200
+ )
201
+ self.surplus_sheet = self.wb.create_sheet(title=_("Sheet Surplus"))
202
+ self.center_alignment = Alignment(
203
+ horizontal="center", vertical="center"
204
+ )
205
+ self.thin_border = Border(
206
+ left=Side(style="thin"),
207
+ right=Side(style="thin"),
208
+ top=Side(style="thin"),
209
+ bottom=Side(style="thin"),
210
+ )
211
+
212
+ self.font_12 = Font(size=12)
213
+ self.font_12_bold = Font(size=12, bold=True)
214
+ self.font_14 = Font(size=14)
215
+ self.font_16 = Font(size=16)
216
+ self.font_16_bold = Font(size=16, bold=True)
217
+ self.font_20_bold = Font(size=20, bold=True)
218
+
219
+ self.request = request
220
+ self.user = self.request.user
221
+ self.meal_type = meal_type
222
+ self.year = int(month.split("-")[0])
223
+ self.month = int(month.split("-")[1])
224
+ self.date_start = datetime(self.year, self.month, 1).date()
225
+ self.date_end = datetime(
226
+ self.year, self.month, calendar.monthrange(self.year, self.month)[1]
227
+ ).date()
228
+ self.is_zh_CN = is_zh_CN()
229
+
230
+ self._is_school = None
231
+
232
+ @property
233
+ def is_school(self):
234
+ if self._is_school:
235
+ return self._is_school
236
+ is_school = (
237
+ (
238
+ any(
239
+ [
240
+ name in self.user.affiliation
241
+ for name in [
242
+ "\u5e7c\u513f\u56ed",
243
+ "\u5c0f\u5b66",
244
+ "\u4e2d\u5b66",
245
+ "\u5927\u5b66",
246
+ ]
247
+ ]
248
+ )
249
+ )
250
+ if self.is_zh_CN
251
+ else False
252
+ )
253
+ self._is_school = is_school
254
+ return self._is_school
255
+
256
+ def fill_in_non_storage_sheet(self):
257
+ sheet = self.non_storage_sheet
258
+ user = self.user
259
+ title_cell = sheet.cell(1, 1)
260
+ title_cell.value = _(
261
+ "Table of {superior_department} Canteen Non-Storaged Ingredients Statistics"
262
+ ).format(
263
+ superior_department=user.superior_department,
264
+ )
265
+ title_cell.font = self.font_16_bold
266
+ title_cell.alignment = self.center_alignment
267
+ for col_num, width in [
268
+ [1, 2.23],
269
+ [2, 3.14],
270
+ [3, 3.14],
271
+ ]:
272
+ set_column_width_in_inches(sheet, col_num, width)
273
+ sheet.merge_cells("A1:C1")
274
+
275
+ sub_title_cell = sheet.cell(2, 1)
276
+ sheet.merge_cells("A2:C2")
277
+ sub_title_cell.font = self.font_12
278
+ sub_title_cell.alignment = self.center_alignment
279
+ sub_title_cell.value = _(
280
+ "Affiliation: {affiliation} Monetary Unit: {year}.{month:0>2}.{day:0>2}"
281
+ ).format(
282
+ affiliation=user.affiliation,
283
+ year=self.year,
284
+ month=self.month,
285
+ day=self.date_end.day,
286
+ )
287
+
288
+ header_row_num = 3
289
+ header_category_cell = sheet.cell(header_row_num, 1)
290
+ header_category_cell.value = _("Ingredient Categories (storage sheet)")
291
+
292
+ header_total_price_cell = sheet.cell(header_row_num, 2)
293
+ header_total_price_cell.value = _(
294
+ "Ingredient Total Prices (storage sheet)"
295
+ )
296
+
297
+ header_note_cell = sheet.cell(header_row_num, 3)
298
+ header_note_cell.value = _("Procurement Note")
299
+
300
+ for cell in [
301
+ header_category_cell,
302
+ header_total_price_cell,
303
+ header_note_cell,
304
+ ]:
305
+ cell.font = self.font_12
306
+ cell.alignment = self.center_alignment
307
+ cell.border = self.thin_border
308
+
309
+ categories = Category.objects.filter(
310
+ Q(user=user) & Q(is_disabled=False)
311
+ ).all()
312
+
313
+ set_row_height_in_inches(sheet, 1, 0.38)
314
+ set_row_height_in_inches(sheet, 2, 0.22)
315
+ set_row_height_in_inches(sheet, 3, 0.32)
316
+
317
+ for index, category in enumerate(categories):
318
+
319
+ row_num = header_row_num + 1 + index
320
+ set_row_height_in_inches(sheet, row_num, 0.32)
321
+
322
+ category_cell = sheet.cell(row_num, 1)
323
+ category_cell.value = category.name
324
+
325
+ ingredients = Ingredient.objects.filter(
326
+ Q(user=user)
327
+ & Q(category=category)
328
+ & Q(storage_date__gte=self.date_start)
329
+ & Q(storage_date__lte=self.date_end)
330
+ & Q(meal_type=self.meal_type)
331
+ & Q(is_disabled=False)
332
+ & Q(is_ignorable=True)
333
+ ).all()
334
+ total_price_cell = sheet.cell(row_num, 2)
335
+ total_price_cell.value = sum([i.total_price for i in ingredients])
336
+
337
+ note_cell = sheet.cell(row_num, 3)
338
+
339
+ for cell in [category_cell, total_price_cell, note_cell]:
340
+ cell.font = self.font_12
341
+ cell.alignment = self.center_alignment
342
+ cell.border = self.thin_border
343
+
344
+ ingredients = Ingredient.objects.filter(
345
+ Q(user=user)
346
+ & Q(storage_date__gte=self.date_start)
347
+ & Q(storage_date__lte=self.date_end)
348
+ & Q(meal_type=self.meal_type)
349
+ & Q(is_disabled=False)
350
+ & Q(is_ignorable=True)
351
+ ).all()
352
+
353
+ summary_row_num = len(categories) + header_row_num + 1
354
+ summary_total_price = sum([i.total_price for i in ingredients])
355
+ summary_total_price_cell = sheet.cell(summary_row_num, 1)
356
+ summary_total_price_cell.border = self.thin_border
357
+ summary_total_price_cell.value = (
358
+ _(
359
+ "Total Price Text: {total_price_text} {total_price}"
360
+ ).format(
361
+ total_price_text=get_CNY_TEXT(summary_total_price),
362
+ total_price=summary_total_price,
363
+ )
364
+ if self.is_zh_CN
365
+ else _(
366
+ "Total Price Text: {total_price_text} {total_price}"
367
+ ).format(
368
+ total_price_text=str(summary_total_price),
369
+ total_price=summary_total_price,
370
+ )
371
+ )
372
+ sheet.merge_cells(f"A{summary_row_num}:C{summary_row_num}")
373
+ set_row_height_in_inches(sheet, summary_row_num, 0.32)
374
+
375
+ handler_row_num = summary_row_num + 1
376
+ handler_cell = sheet.cell(handler_row_num, 1)
377
+ handler_cell.border = self.thin_border
378
+ handler_cell.value = _("Handler:")
379
+ sheet.merge_cells(f"A{handler_row_num}:C{handler_row_num}")
380
+ set_row_height_in_inches(sheet, handler_row_num, 0.32)
381
+
382
+ reviewer_row_num = handler_row_num + 1
383
+ reviewer_cell = sheet.cell(reviewer_row_num, 1)
384
+ reviewer_cell.border = self.thin_border
385
+ reviewer_cell.value = _("Reviewer:")
386
+ sheet.merge_cells(f"A{reviewer_row_num}:C{reviewer_row_num}")
387
+ set_row_height_in_inches(sheet, reviewer_row_num, 0.32)
388
+
389
+ supervisor_row_num = reviewer_row_num + 1
390
+ supervisor_cell = sheet.cell(supervisor_row_num, 1)
391
+ supervisor_cell.border = self.thin_border
392
+ supervisor_cell.value = (
393
+ _("Principal's Signature:")
394
+ if self.is_school
395
+ else _("Supervisor's Signature:")
396
+ )
397
+ sheet.merge_cells(f"A{supervisor_row_num}:C{supervisor_row_num}")
398
+ set_row_height_in_inches(sheet, supervisor_row_num, 0.32)
399
+
400
+ note_row_num = supervisor_row_num + 1
401
+ note_cell = sheet.cell(note_row_num, 1)
402
+ note_cell.border = self.thin_border
403
+ note_cell.value = (
404
+ _(
405
+ "Note: This form is a summary of all monthly food and "
406
+ + "material inventory receipts from the cafeteria. After "
407
+ + "verification, it will be signed and stamped with "
408
+ + "the school seal by the principal as reimbursement "
409
+ + "evidence."
410
+ )
411
+ if self.is_school
412
+ else _(
413
+ "Note: This form is a summary of all monthly food and "
414
+ + "material inventory receipts from the cafeteria. "
415
+ + "After verification, it will be signed and stamped "
416
+ + "with the affiliation seal by the supervisor as "
417
+ + "reimbursement evidence."
418
+ )
419
+ )
420
+ sheet.merge_cells(f"A{note_row_num}:C{note_row_num}")
421
+ set_row_height_in_inches(sheet, note_row_num, 0.27)
422
+
423
+ def fill_in_consumption_sheet(self):
424
+ sheet = self.consumption_sheet
425
+ user = self.user
426
+ title_cell = sheet.cell(1, 1)
427
+ title_cell.value = _(
428
+ "Table of {superior_department} Canteen Consumed Ingredients Statistics"
429
+ ).format(
430
+ superior_department=user.superior_department,
431
+ )
432
+ title_cell.font = self.font_16_bold
433
+ title_cell.alignment = self.center_alignment
434
+ for col_num, width in [
435
+ [1, 2.23],
436
+ [2, 3.14],
437
+ [3, 3.14],
438
+ ]:
439
+ set_column_width_in_inches(sheet, col_num, width)
440
+ sheet.merge_cells("A1:C1")
441
+
442
+ sub_title_cell = sheet.cell(2, 1)
443
+ sheet.merge_cells("A2:C2")
444
+ sub_title_cell.font = self.font_12
445
+ sub_title_cell.alignment = self.center_alignment
446
+ sub_title_cell.value = _(
447
+ "Affiliation: {affiliation} Monetary Unit: {year}.{month:0>2}.{day:0>2}"
448
+ ).format(
449
+ affiliation=user.affiliation,
450
+ year=self.year,
451
+ month=self.month,
452
+ day=self.date_end.day,
453
+ )
454
+
455
+ header_row_num = 3
456
+ header_category_cell = sheet.cell(header_row_num, 1)
457
+ header_category_cell.value = _(
458
+ "Ingredient Categories (Consumption Sheet)"
459
+ )
460
+
461
+ header_total_price_cell = sheet.cell(header_row_num, 2)
462
+ header_total_price_cell.value = _(
463
+ "Ingredient Total Prices (Consumption Sheet)"
464
+ )
465
+
466
+ header_note_cell = sheet.cell(header_row_num, 3)
467
+ header_note_cell.value = _("Procurement Note (Consumption Sheet)")
468
+
469
+ for cell in [
470
+ header_category_cell,
471
+ header_total_price_cell,
472
+ header_note_cell,
473
+ ]:
474
+ cell.font = self.font_12
475
+ cell.alignment = self.center_alignment
476
+ cell.border = self.thin_border
477
+
478
+ categories = Category.objects.filter(
479
+ Q(user=user) & Q(is_disabled=False)
480
+ ).all()
481
+
482
+ set_row_height_in_inches(sheet, 1, 0.38)
483
+ set_row_height_in_inches(sheet, 2, 0.22)
484
+ set_row_height_in_inches(sheet, 3, 0.32)
485
+
486
+ for index, category in enumerate(categories):
487
+
488
+ row_num = header_row_num + 1 + index
489
+ set_row_height_in_inches(sheet, row_num, 0.32)
490
+
491
+ category_cell = sheet.cell(row_num, 1)
492
+ category_cell.value = category.name
493
+
494
+ ingredients = Ingredient.objects.filter(
495
+ Q(user=user)
496
+ & Q(category=category)
497
+ & Q(
498
+ consumptions__date_of_using__range=(
499
+ self.date_start,
500
+ self.date_end,
501
+ )
502
+ )
503
+ & Q(meal_type=self.meal_type)
504
+ & Q(is_disabled=False)
505
+ & Q(is_ignorable=False)
506
+ ).distinct()
507
+ total_price_cell = sheet.cell(row_num, 2)
508
+ total_price_consumed = Decimal("0.0")
509
+ for i in ingredients:
510
+ consumptions = i.consumptions.filter(is_disabled=False).all()
511
+ total_price_consumed += sum(
512
+ [c.amount_used * i.unit_price for c in consumptions]
513
+ )
514
+ total_price_cell.value = total_price_consumed
515
+
516
+ note_cell = sheet.cell(row_num, 3)
517
+
518
+ for cell in [category_cell, total_price_cell, note_cell]:
519
+ cell.font = self.font_12
520
+ cell.alignment = self.center_alignment
521
+ cell.border = self.thin_border
522
+
523
+ ingredients = Ingredient.objects.filter(
524
+ Q(user=user)
525
+ & Q(
526
+ consumptions__date_of_using__range=(
527
+ self.date_start,
528
+ self.date_end,
529
+ )
530
+ )
531
+ & Q(meal_type=self.meal_type)
532
+ & Q(category__is_disabled=False)
533
+ & Q(is_disabled=False)
534
+ & Q(is_ignorable=False)
535
+ ).all()
536
+
537
+ summary_row_num = len(categories) + header_row_num + 1
538
+ summary_total_price = Decimal("0.0")
539
+ for i in ingredients:
540
+ consumptions = i.consumptions.filter(Q(is_disabled=False)).all()
541
+ summary_total_price += sum(
542
+ [c.amount_used * i.unit_price for c in consumptions]
543
+ )
544
+ summary_total_price_cell = sheet.cell(summary_row_num, 1)
545
+ total_price_cell.border = self.thin_border
546
+ summary_total_price_cell.value = (
547
+ _(
548
+ "Total Price Text: {total_price_text} {total_price}"
549
+ ).format(
550
+ total_price_text=get_CNY_TEXT(summary_total_price),
551
+ total_price=summary_total_price,
552
+ )
553
+ if self.is_zh_CN
554
+ else _(
555
+ "Total Price Text: {total_price_text} {total_price}"
556
+ ).format(
557
+ total_price_text=str(summary_total_price),
558
+ total_price=summary_total_price,
559
+ )
560
+ )
561
+ sheet.merge_cells(f"A{summary_row_num}:C{summary_row_num}")
562
+ set_row_height_in_inches(sheet, summary_row_num, 0.32)
563
+
564
+ handler_row_num = summary_row_num + 1
565
+ handler_cell = sheet.cell(handler_row_num, 1)
566
+ handler_cell.border = self.thin_border
567
+ handler_cell.value = _("Handler:")
568
+ sheet.merge_cells(f"A{handler_row_num}:C{handler_row_num}")
569
+ set_row_height_in_inches(sheet, handler_row_num, 0.32)
570
+
571
+ reviewer_row_num = handler_row_num + 1
572
+ reviewer_cell = sheet.cell(reviewer_row_num, 1)
573
+ reviewer_cell.border = self.thin_border
574
+ reviewer_cell.value = _("Reviewer:")
575
+ sheet.merge_cells(f"A{reviewer_row_num}:C{reviewer_row_num}")
576
+ set_row_height_in_inches(sheet, reviewer_row_num, 0.32)
577
+
578
+ supervisor_row_num = reviewer_row_num + 1
579
+ supervisor_cell = sheet.cell(supervisor_row_num, 1)
580
+ supervisor_cell.border = self.thin_border
581
+ supervisor_cell.value = (
582
+ _("Principal's Signature:")
583
+ if self.is_school
584
+ else _("Supervisor's Signature:")
585
+ )
586
+ sheet.merge_cells(f"A{supervisor_row_num}:C{supervisor_row_num}")
587
+ set_row_height_in_inches(sheet, supervisor_row_num, 0.32)
588
+
589
+ note_row_num = supervisor_row_num + 1
590
+ note_cell = sheet.cell(note_row_num, 1)
591
+ note_cell.border = self.thin_border
592
+ note_cell.value = (
593
+ _(
594
+ "Note: This form is a summary of all monthly food and "
595
+ + "material consumption receipts from the cafeteria. After "
596
+ + "verification, it will be signed and stamped with "
597
+ + "the school seal by the principal as reimbursement "
598
+ + "evidence."
599
+ )
600
+ if self.is_school
601
+ else _(
602
+ "Note: This form is a summary of all monthly food and "
603
+ + "material consumption receipts from the cafeteria. "
604
+ + "After verification, it will be signed and stamped "
605
+ + "with the affiliation seal by the supervisor as "
606
+ + "reimbursement evidence."
607
+ )
608
+ )
609
+ sheet.merge_cells(f"A{note_row_num}:C{note_row_num}")
610
+ set_row_height_in_inches(sheet, note_row_num, 0.27)
611
+
612
+ def fill_in_storage_sheet(self):
613
+ sheet = self.storage_sheet
614
+ user = self.user
615
+ title_cell = sheet.cell(1, 1)
616
+ title_cell.value = _(
617
+ "Table of {superior_department} Canteen Storaged Ingredients Statistics"
618
+ ).format(
619
+ superior_department=user.superior_department,
620
+ )
621
+ title_cell.font = self.font_16_bold
622
+ title_cell.alignment = self.center_alignment
623
+ for col_num, width in [
624
+ [1, 2.23],
625
+ [2, 3.14],
626
+ [3, 3.14],
627
+ ]:
628
+ set_column_width_in_inches(sheet, col_num, width)
629
+ sheet.merge_cells("A1:C1")
630
+
631
+ sub_title_cell = sheet.cell(2, 1)
632
+ sheet.merge_cells("A2:C2")
633
+ sub_title_cell.font = self.font_12
634
+ sub_title_cell.alignment = self.center_alignment
635
+ sub_title_cell.value = _(
636
+ "Affiliation: {affiliation} Monetary Unit: {year}.{month:0>2}.{day:0>2}"
637
+ ).format(
638
+ affiliation=user.affiliation,
639
+ year=self.year,
640
+ month=self.month,
641
+ day=self.date_end.day,
642
+ )
643
+
644
+ header_row_num = 3
645
+ header_category_cell = sheet.cell(header_row_num, 1)
646
+ header_category_cell.value = _("Ingredient Categories (storage sheet)")
647
+
648
+ header_total_price_cell = sheet.cell(header_row_num, 2)
649
+ header_total_price_cell.value = _(
650
+ "Ingredient Total Prices (storage sheet)"
651
+ )
652
+
653
+ header_note_cell = sheet.cell(header_row_num, 3)
654
+ header_note_cell.value = _("Procurement Note")
655
+
656
+ for cell in [
657
+ header_category_cell,
658
+ header_total_price_cell,
659
+ header_note_cell,
660
+ ]:
661
+ cell.font = self.font_12
662
+ cell.alignment = self.center_alignment
663
+ cell.border = self.thin_border
664
+
665
+ categories = Category.objects.filter(
666
+ Q(user=user) & Q(is_disabled=False)
667
+ ).all()
668
+
669
+ set_row_height_in_inches(sheet, 1, 0.38)
670
+ set_row_height_in_inches(sheet, 2, 0.22)
671
+ set_row_height_in_inches(sheet, 3, 0.32)
672
+
673
+ for index, category in enumerate(categories):
674
+
675
+ row_num = header_row_num + 1 + index
676
+ set_row_height_in_inches(sheet, row_num, 0.32)
677
+
678
+ category_cell = sheet.cell(row_num, 1)
679
+ category_cell.value = category.name
680
+
681
+ ingredients = Ingredient.objects.filter(
682
+ Q(user=user)
683
+ & Q(category=category)
684
+ & Q(storage_date__gte=self.date_start)
685
+ & Q(storage_date__lte=self.date_end)
686
+ & Q(meal_type=self.meal_type)
687
+ & Q(is_disabled=False)
688
+ & Q(is_ignorable=False)
689
+ ).all()
690
+ total_price_cell = sheet.cell(row_num, 2)
691
+ total_price_cell.value = sum([i.total_price for i in ingredients])
692
+
693
+ note_cell = sheet.cell(row_num, 3)
694
+
695
+ for cell in [category_cell, total_price_cell, note_cell]:
696
+ cell.font = self.font_12
697
+ cell.alignment = self.center_alignment
698
+ cell.border = self.thin_border
699
+
700
+ ingredients = Ingredient.objects.filter(
701
+ Q(user=user)
702
+ & Q(storage_date__gte=self.date_start)
703
+ & Q(storage_date__lte=self.date_end)
704
+ & Q(meal_type=self.meal_type)
705
+ & Q(is_disabled=False)
706
+ & Q(is_ignorable=False)
707
+ ).all()
708
+
709
+ summary_row_num = len(categories) + header_row_num + 1
710
+ summary_total_price = sum([i.total_price for i in ingredients])
711
+ summary_total_price_cell = sheet.cell(summary_row_num, 1)
712
+ total_price_cell.border = self.thin_border
713
+ summary_total_price_cell.value = (
714
+ _(
715
+ "Total Price Text: {total_price_text} {total_price}"
716
+ ).format(
717
+ total_price_text=get_CNY_TEXT(summary_total_price),
718
+ total_price=summary_total_price,
719
+ )
720
+ if self.is_zh_CN
721
+ else _(
722
+ "Total Price Text: {total_price_text} {total_price}"
723
+ ).format(
724
+ total_price_text=str(summary_total_price),
725
+ total_price=summary_total_price,
726
+ )
727
+ )
728
+ sheet.merge_cells(f"A{summary_row_num}:C{summary_row_num}")
729
+ set_row_height_in_inches(sheet, summary_row_num, 0.32)
730
+
731
+ handler_row_num = summary_row_num + 1
732
+ handler_cell = sheet.cell(handler_row_num, 1)
733
+ handler_cell.border = self.thin_border
734
+ handler_cell.value = _("Handler:")
735
+ sheet.merge_cells(f"A{handler_row_num}:C{handler_row_num}")
736
+ set_row_height_in_inches(sheet, handler_row_num, 0.32)
737
+
738
+ reviewer_row_num = handler_row_num + 1
739
+ reviewer_cell = sheet.cell(reviewer_row_num, 1)
740
+ reviewer_cell.border = self.thin_border
741
+ reviewer_cell.value = _("Reviewer:")
742
+ sheet.merge_cells(f"A{reviewer_row_num}:C{reviewer_row_num}")
743
+ set_row_height_in_inches(sheet, reviewer_row_num, 0.32)
744
+
745
+ supervisor_row_num = reviewer_row_num + 1
746
+ supervisor_cell = sheet.cell(supervisor_row_num, 1)
747
+ supervisor_cell.border = self.thin_border
748
+ supervisor_cell.value = (
749
+ _("Principal's Signature:")
750
+ if self.is_school
751
+ else _("Supervisor's Signature:")
752
+ )
753
+ sheet.merge_cells(f"A{supervisor_row_num}:C{supervisor_row_num}")
754
+ set_row_height_in_inches(sheet, supervisor_row_num, 0.32)
755
+
756
+ note_row_num = supervisor_row_num + 1
757
+ note_cell = sheet.cell(note_row_num, 1)
758
+ note_cell.border = self.thin_border
759
+ note_cell.value = (
760
+ _(
761
+ "Note: This form is a summary of all monthly food and "
762
+ + "material inventory receipts from the cafeteria. After "
763
+ + "verification, it will be signed and stamped with "
764
+ + "the school seal by the principal as reimbursement "
765
+ + "evidence."
766
+ )
767
+ if self.is_school
768
+ else _(
769
+ "Note: This form is a summary of all monthly food and "
770
+ + "material inventory receipts from the cafeteria. "
771
+ + "After verification, it will be signed and stamped "
772
+ + "with the affiliation seal by the supervisor as "
773
+ + "reimbursement evidence."
774
+ )
775
+ )
776
+ sheet.merge_cells(f"A{note_row_num}:C{note_row_num}")
777
+ set_row_height_in_inches(sheet, note_row_num, 0.27)
778
+
779
+ def fill_in_consumption_list_sheet(self):
780
+ sheet = self.consumption_list_sheet
781
+ user = self.user
782
+ consumption_rows_count = 21
783
+ categories = Category.objects.filter(
784
+ Q(user=user) & Q(is_disabled=False)
785
+ ).all()
786
+ consumptions = Consumption.objects.filter(
787
+ Q(is_disabled=False)
788
+ & Q(ingredient__meal_type=self.meal_type)
789
+ & Q(ingredient__user=user)
790
+ & Q(date_of_using__gte=self.date_start)
791
+ & Q(date_of_using__lte=self.date_end)
792
+ ).all()
793
+ consumption_row_height = 0.18
794
+ consumption_rows_height = consumption_rows_count * 0.18
795
+
796
+ consumption_dates = list(set([c.date_of_using for c in consumptions]))
797
+ consumption_dates = sorted(consumption_dates)
798
+
799
+ formed_consumptions = []
800
+ for consumption_date in consumption_dates:
801
+ dated_consumptions = [
802
+ c for c in consumptions if c.date_of_using == consumption_date
803
+ ]
804
+ dated_consumptions = sorted(
805
+ dated_consumptions,
806
+ key=lambda i: (i.ingredient.category.name, i.ingredient.name),
807
+ )
808
+
809
+ dated_consumption_categories = list(
810
+ set([c.ingredient.category for c in dated_consumptions])
811
+ )
812
+ empty_categories = [
813
+ c for c in categories if not c in dated_consumption_categories
814
+ ]
815
+ same_date_count = math.ceil(
816
+ len(dated_consumptions)
817
+ / (consumption_rows_count - len(empty_categories))
818
+ )
819
+ step = math.floor(len(dated_consumptions) / same_date_count)
820
+ sub_consumption_num = 1
821
+ for index in range(0, len(dated_consumptions), step):
822
+ split_dated_consumptions = dated_consumptions[
823
+ index : index + step
824
+ ]
825
+ split_dated_consumption = split_dated_consumptions[0]
826
+ split_dated_consumption_categories = list(
827
+ set(
828
+ [
829
+ c.ingredient.category
830
+ for c in split_dated_consumptions
831
+ ]
832
+ )
833
+ )
834
+ split_empty_categories = [
835
+ c
836
+ for c in split_dated_consumption_categories
837
+ if not c in categories
838
+ ]
839
+ split_empty_categories = split_empty_categories + [
840
+ random.choice(categories)
841
+ for i in range(
842
+ consumption_rows_count
843
+ - len(split_dated_consumptions)
844
+ - len(split_empty_categories)
845
+ )
846
+ ]
847
+
848
+ fake_ingredients = [
849
+ Ingredient(
850
+ user=user,
851
+ name="",
852
+ storage_date=self.date_start,
853
+ meal_type=split_dated_consumption.ingredient.meal_type,
854
+ category=c,
855
+ quantity=0.0,
856
+ quantity_unit_name="",
857
+ total_price=0.0,
858
+ is_ignorable=False,
859
+ )
860
+ for c in split_empty_categories
861
+ ]
862
+
863
+ for fake_ingredient in fake_ingredients:
864
+ split_dated_consumptions.append(
865
+ Consumption(
866
+ ingredient=fake_ingredient,
867
+ date_of_using=consumption_date,
868
+ amount_used=Decimal("0"),
869
+ is_disabled=False,
870
+ )
871
+ )
872
+
873
+ split_dated_consumptions = sorted(
874
+ split_dated_consumptions,
875
+ key=lambda i: (i.ingredient.category.name),
876
+ )
877
+
878
+ consumption_date_index = sub_consumption_num
879
+ formed_consumptions.append(
880
+ [
881
+ consumption_date,
882
+ consumption_date_index,
883
+ split_dated_consumptions,
884
+ ]
885
+ )
886
+
887
+ sub_consumption_num += 1
888
+
889
+ consumption_num = 0
890
+ for index, (
891
+ consumption_date,
892
+ consumption_date_index,
893
+ dated_consumptions,
894
+ ) in enumerate(formed_consumptions):
895
+ row_num = (consumption_rows_count + 6) * index + 1
896
+
897
+ title_cell_row_num = row_num
898
+ title_cell = sheet.cell(title_cell_row_num, 1)
899
+ title_cell.value = _("Storage List (Storage Sheet)")
900
+ title_cell.alignment = self.center_alignment
901
+ title_cell.font = self.font_20_bold
902
+ sheet.merge_cells(f"A{title_cell_row_num}:H{title_cell_row_num}")
903
+
904
+ sub_title_affiliation_cell_row_num = title_cell_row_num + 1
905
+ sub_title_affiliation_cell = sheet.cell(
906
+ sub_title_affiliation_cell_row_num, 1
907
+ )
908
+ sub_title_affiliation_cell.font = self.font_12
909
+ sub_title_affiliation_cell.value = (
910
+ _("Affiliation Name: {affiliation_name}")
911
+ if self.is_school
912
+ else _("Principal Name: {affiliation_name}")
913
+ ).format(affiliation_name=user.affiliation)
914
+
915
+ sub_title_date_and_unit_cell_row_num = title_cell_row_num + 1
916
+ sub_title_date_and_unit_cell = sheet.cell(
917
+ sub_title_date_and_unit_cell_row_num, 4
918
+ )
919
+ sub_title_date_and_unit_cell.font = self.font_12
920
+ sub_title_date_and_unit_cell.value = (
921
+ _("{day:0>2} {month:0>2} {year} Quantity Unit Name: CNY")
922
+ ).format(year=self.year, month=self.month, day=consumption_date.day)
923
+
924
+ if consumption_date_index < 2:
925
+ consumption_num += 1
926
+
927
+ prev_consumption_date = (
928
+ formed_consumptions[index - 1][0]
929
+ if 0 <= index - 1 < len(formed_consumptions)
930
+ else None
931
+ )
932
+ next_consumption_date = (
933
+ formed_consumptions[index + 1][0]
934
+ if 0 < index + 1 < (len(formed_consumptions))
935
+ else None
936
+ )
937
+ sub_title_num_cell_row_num = title_cell_row_num + 1
938
+ sub_title_num_cell = sheet.cell(sub_title_num_cell_row_num, 7)
939
+ sub_title_num_cell.font = self.font_12
940
+ sub_title_num_cell.value = _(
941
+ "Storage No. {consumption_num}"
942
+ ).format(
943
+ consumption_num=(
944
+ f"C{self.month:0>2}{consumption_num:0>2}"
945
+ if self.is_zh_CN
946
+ else f"C{self.month:0>2}{consumption_num:0>2}"
947
+ )
948
+ ) + (
949
+ _("(Sub Storage No. {sub_consumption_num})").format(
950
+ sub_consumption_num=consumption_date_index
951
+ )
952
+ if (
953
+ (
954
+ next_consumption_date
955
+ and next_consumption_date == consumption_date
956
+ )
957
+ or (
958
+ prev_consumption_date
959
+ and prev_consumption_date == consumption_date
960
+ )
961
+ )
962
+ else ""
963
+ )
964
+
965
+ sheet.merge_cells(
966
+ f"A{sub_title_affiliation_cell_row_num}:B{sub_title_affiliation_cell_row_num}"
967
+ )
968
+ sheet.merge_cells(
969
+ f"D{sub_title_date_and_unit_cell_row_num}:F{sub_title_date_and_unit_cell_row_num}"
970
+ )
971
+ sheet.merge_cells(
972
+ f"G{sub_title_num_cell_row_num}:H{sub_title_num_cell_row_num}"
973
+ )
974
+
975
+ font_12_cells = []
976
+ header_row_num = title_cell_row_num + 2
977
+ category_header_cell = sheet.cell(header_row_num, 1)
978
+ category_header_cell.value = _("Category (Consumption List Sheet)")
979
+
980
+ ingredient_name_header_cell = sheet.cell(header_row_num, 2)
981
+ ingredient_name_header_cell.value = _(
982
+ "Ingredient Name (Consumption List Sheet)"
983
+ )
984
+
985
+ quantity_unit_name_header_cell = sheet.cell(header_row_num, 3)
986
+ quantity_unit_name_header_cell.value = _(
987
+ "Quantity Unit Name (Consumption List Sheet)"
988
+ )
989
+
990
+ quantity_header_cell = sheet.cell(header_row_num, 4)
991
+ quantity_header_cell.value = _("Quantity (Consumption List Sheet)")
992
+
993
+ unit_price_header_cell = sheet.cell(header_row_num, 5)
994
+ unit_price_header_cell.value = _(
995
+ "Unit Price (Consumption List Sheet)"
996
+ )
997
+
998
+ total_price_header_cell = sheet.cell(header_row_num, 6)
999
+ total_price_header_cell.value = _(
1000
+ "Total Price (Consumption List Sheet)"
1001
+ )
1002
+
1003
+ ingredients_total_price_header_cell = sheet.cell(header_row_num, 7)
1004
+ ingredients_total_price_header_cell.value = _(
1005
+ "Ingredients Total Price (Consumption List Sheet)"
1006
+ )
1007
+
1008
+ note_header_cell = sheet.cell(header_row_num, 8)
1009
+ note_header_cell.value = _("Note (Consumption List Sheet)")
1010
+
1011
+ font_12_cells += [
1012
+ category_header_cell,
1013
+ ingredient_name_header_cell,
1014
+ quantity_unit_name_header_cell,
1015
+ quantity_header_cell,
1016
+ unit_price_header_cell,
1017
+ total_price_header_cell,
1018
+ ingredients_total_price_header_cell,
1019
+ note_header_cell,
1020
+ ]
1021
+ for cell in font_12_cells:
1022
+ cell.alignment = self.center_alignment
1023
+ cell.font = self.font_12
1024
+ cell.border = self.thin_border
1025
+
1026
+ last_category = None
1027
+
1028
+ first_consumption_row_num = header_row_num + 1
1029
+ for row_num in range(
1030
+ first_consumption_row_num,
1031
+ first_consumption_row_num + len(dated_consumptions) + 1,
1032
+ ):
1033
+ for col_num in range(1, 9):
1034
+ cell = sheet.cell(row_num, col_num)
1035
+ cell.font = self.font_12
1036
+ cell.alignment = self.center_alignment
1037
+ cell.border = self.thin_border
1038
+
1039
+ for index, consumption in enumerate(dated_consumptions):
1040
+ consumption_row_num = header_row_num + 1 + index
1041
+
1042
+ if not consumption.ingredient.category == last_category:
1043
+ sheet.cell(
1044
+ consumption_row_num,
1045
+ 1,
1046
+ consumption.ingredient.category.name,
1047
+ )
1048
+ sheet.cell(
1049
+ consumption_row_num,
1050
+ 7,
1051
+ sum(
1052
+ [
1053
+ c.ingredient.unit_price * c.amount_used
1054
+ for c in dated_consumptions
1055
+ if c.ingredient.category
1056
+ == consumption.ingredient.category
1057
+ ]
1058
+ )
1059
+ or "",
1060
+ )
1061
+ consumptions_same_category_len = len(
1062
+ [
1063
+ c
1064
+ for c in dated_consumptions
1065
+ if c.ingredient.category
1066
+ == consumption.ingredient.category
1067
+ ]
1068
+ )
1069
+ sheet.merge_cells(
1070
+ f"A{consumption_row_num}:A{consumptions_same_category_len+consumption_row_num-1}"
1071
+ )
1072
+ sheet.merge_cells(
1073
+ f"G{consumption_row_num}:G{consumptions_same_category_len+consumption_row_num-1}"
1074
+ )
1075
+ last_category = consumption.ingredient.category
1076
+
1077
+ sheet.cell(consumption_row_num, 2, consumption.ingredient.name)
1078
+ sheet.cell(
1079
+ consumption_row_num,
1080
+ 3,
1081
+ consumption.ingredient.quantity_unit_name,
1082
+ )
1083
+ sheet.cell(
1084
+ consumption_row_num,
1085
+ 4,
1086
+ (
1087
+ consumption.ingredient.quantity
1088
+ if consumption.ingredient.quantity
1089
+ else ""
1090
+ ),
1091
+ )
1092
+ sheet.cell(
1093
+ consumption_row_num,
1094
+ 5,
1095
+ (
1096
+ consumption.ingredient.unit_price
1097
+ if consumption.ingredient.unit_price
1098
+ else ""
1099
+ ),
1100
+ )
1101
+ sheet.cell(
1102
+ consumption_row_num,
1103
+ 6,
1104
+ (
1105
+ consumption.ingredient.unit_price
1106
+ * consumption.amount_used
1107
+ if consumption.ingredient.unit_price
1108
+ and consumption.amount_used
1109
+ else ""
1110
+ ),
1111
+ )
1112
+ set_row_height_in_inches(
1113
+ sheet, consumption_row_num, consumption_row_height
1114
+ )
1115
+
1116
+ summary_total_price = sum(
1117
+ [
1118
+ c.ingredient.unit_price * c.amount_used
1119
+ for c in dated_consumptions
1120
+ ]
1121
+ )
1122
+ summary_row_num = header_row_num + len(dated_consumptions) + 1
1123
+ sheet.cell(summary_row_num, 1, _("Summary (Storage List Sheet)"))
1124
+ sheet.cell(summary_row_num, 6, summary_total_price)
1125
+ sheet.cell(summary_row_num, 7, summary_total_price)
1126
+
1127
+ summary_row_height = consumption_row_height
1128
+ set_row_height_in_inches(sheet, summary_row_num, summary_row_height)
1129
+
1130
+ signature_row_num = summary_row_num + 1
1131
+ signature_cell = sheet.cell(signature_row_num, 1)
1132
+ signature_cell.value = _(
1133
+ " Reviewer: Handler:{handler}   Weigher: Warehouseman:  "
1134
+ ).format(handler=user.username)
1135
+ signature_cell.font = self.font_14
1136
+ signature_cell.alignment = self.center_alignment
1137
+ sheet.merge_cells(f"A{signature_row_num}:H{signature_row_num}")
1138
+ set_row_height_in_inches(sheet, signature_row_num, 0.22)
1139
+
1140
+ for col_num, col_width in [
1141
+ [1, 1.13],
1142
+ [2, 1.98],
1143
+ [3, 0.85],
1144
+ [4, 0.85],
1145
+ [5, 0.88],
1146
+ [6, 1.28],
1147
+ [7, 1.19],
1148
+ [8, 0.78],
1149
+ ]:
1150
+ set_column_width_in_inches(sheet, col_num, col_width)
1151
+
1152
+ def fill_in_storage_list_sheet(self):
1153
+ sheet = self.storage_list_sheet
1154
+ user = self.user
1155
+ ingredient_rows_count = 21
1156
+ ingredients = Ingredient.objects.filter(
1157
+ Q(user=user)
1158
+ & Q(is_disabled=False)
1159
+ & Q(is_ignorable=False)
1160
+ & Q(storage_date__gte=self.date_start)
1161
+ & Q(storage_date__lte=self.date_end)
1162
+ & Q(meal_type=self.meal_type)
1163
+ ).all()
1164
+ categories = Category.objects.filter(
1165
+ Q(user=user) & Q(is_disabled=False)
1166
+ ).all()
1167
+ ingredient_row_height = 0.18
1168
+ ingredient_rows_height = ingredient_rows_count * 0.18
1169
+ storage_dates = list(set([i.storage_date for i in ingredients]))
1170
+
1171
+ storaged_ingredients = []
1172
+ for storage_date in storage_dates:
1173
+ dated_ingredients = [
1174
+ i for i in ingredients if i.storage_date == storage_date
1175
+ ]
1176
+ dated_ingredients = sorted(
1177
+ dated_ingredients, key=lambda i: (i.category.name, i.name)
1178
+ )
1179
+ dated_ingredient_categories = list(
1180
+ set([i.category for i in ingredients])
1181
+ )
1182
+ empty_categories = [
1183
+ c for c in categories if not c in dated_ingredient_categories
1184
+ ]
1185
+ same_date_count = math.ceil(
1186
+ len(dated_ingredients)
1187
+ / (ingredient_rows_count - len(empty_categories))
1188
+ )
1189
+ step = math.floor(len(dated_ingredients) / same_date_count)
1190
+ sub_storage_num = 1
1191
+ for index in range(0, len(dated_ingredients), step):
1192
+ split_dated_ingredients = dated_ingredients[
1193
+ index : index + step
1194
+ ]
1195
+ split_dated_ingredient = split_dated_ingredients[0]
1196
+ split_dated_ingredient_categories = list(
1197
+ set([i.category for i in split_dated_ingredients])
1198
+ )
1199
+ split_empty_categories = [
1200
+ c
1201
+ for c in split_dated_ingredient_categories
1202
+ if not c in categories
1203
+ ]
1204
+ split_empty_categories = split_empty_categories + [
1205
+ random.choice(categories)
1206
+ for i in range(
1207
+ ingredient_rows_count
1208
+ - len(split_dated_ingredients)
1209
+ - len(split_empty_categories)
1210
+ )
1211
+ ]
1212
+
1213
+ split_dated_ingredients += [
1214
+ Ingredient(
1215
+ user=user,
1216
+ name="",
1217
+ storage_date=storage_date,
1218
+ meal_type=split_dated_ingredient.meal_type,
1219
+ category=c,
1220
+ quantity=0.0,
1221
+ quantity_unit_name="",
1222
+ total_price=0.0,
1223
+ is_ignorable=False,
1224
+ )
1225
+ for c in split_empty_categories
1226
+ ]
1227
+
1228
+ split_dated_ingredients = sorted(
1229
+ split_dated_ingredients, key=lambda i: (i.category.name)
1230
+ )
1231
+
1232
+ storage_date_index = sub_storage_num
1233
+ storaged_ingredients.append(
1234
+ [storage_date, storage_date_index, split_dated_ingredients]
1235
+ )
1236
+
1237
+ sub_storage_num += 1
1238
+
1239
+ storage_num = 0
1240
+ for index, (
1241
+ storage_date,
1242
+ storage_date_index,
1243
+ dated_ingredients,
1244
+ ) in enumerate(storaged_ingredients):
1245
+ row_num = (ingredient_rows_count + 6) * index + 1
1246
+
1247
+ title_cell_row_num = row_num
1248
+ title_cell = sheet.cell(title_cell_row_num, 1)
1249
+ title_cell.value = _("Storage List (Storage Sheet)")
1250
+ title_cell.alignment = self.center_alignment
1251
+ title_cell.font = self.font_20_bold
1252
+ sheet.merge_cells(f"A{title_cell_row_num}:H{title_cell_row_num}")
1253
+
1254
+ sub_title_affiliation_cell_row_num = title_cell_row_num + 1
1255
+ sub_title_affiliation_cell = sheet.cell(
1256
+ sub_title_affiliation_cell_row_num, 1
1257
+ )
1258
+ sub_title_affiliation_cell.font = self.font_12
1259
+ sub_title_affiliation_cell.value = (
1260
+ _("Affiliation Name: {affiliation_name}")
1261
+ if self.is_school
1262
+ else _("Principal Name: {affiliation_name}")
1263
+ ).format(affiliation_name=user.affiliation)
1264
+
1265
+ sub_title_date_and_unit_cell_row_num = title_cell_row_num + 1
1266
+ sub_title_date_and_unit_cell = sheet.cell(
1267
+ sub_title_date_and_unit_cell_row_num, 4
1268
+ )
1269
+ sub_title_date_and_unit_cell.font = self.font_12
1270
+ sub_title_date_and_unit_cell.value = (
1271
+ _("{day:0>2} {month:0>2} {year} Quantity Unit Name: CNY")
1272
+ ).format(year=self.year, month=self.month, day=storage_date.day)
1273
+
1274
+ if storage_date_index < 2:
1275
+ storage_num += 1
1276
+
1277
+ prev_storage_date = (
1278
+ storaged_ingredients[index - 1][0]
1279
+ if 0 <= index - 1 < len(storaged_ingredients)
1280
+ else None
1281
+ )
1282
+ next_storage_date = (
1283
+ storaged_ingredients[index + 1][0]
1284
+ if 0 < index + 1 < (len(storaged_ingredients) - 1)
1285
+ else None
1286
+ )
1287
+ sub_title_num_cell_row_num = title_cell_row_num + 1
1288
+ sub_title_num_cell = sheet.cell(sub_title_num_cell_row_num, 7)
1289
+ sub_title_num_cell.font = self.font_12
1290
+ sub_title_num_cell.value = _("Storage No. {storage_num}").format(
1291
+ storage_num=(
1292
+ f"R{self.month:0>2}{storage_num:0>2}"
1293
+ if self.is_zh_CN
1294
+ else f"S{self.month:0>2}{storage_num:0>2}"
1295
+ )
1296
+ ) + (
1297
+ _("(Sub Storage No. {sub_storage_num})").format(
1298
+ sub_storage_num=storage_date_index
1299
+ )
1300
+ if (
1301
+ (next_storage_date and next_storage_date == storage_date)
1302
+ or (prev_storage_date and prev_storage_date == storage_date)
1303
+ )
1304
+ else ""
1305
+ )
1306
+
1307
+ sheet.merge_cells(
1308
+ f"A{sub_title_affiliation_cell_row_num}:B{sub_title_affiliation_cell_row_num}"
1309
+ )
1310
+ sheet.merge_cells(
1311
+ f"D{sub_title_date_and_unit_cell_row_num}:F{sub_title_date_and_unit_cell_row_num}"
1312
+ )
1313
+ sheet.merge_cells(
1314
+ f"G{sub_title_num_cell_row_num}:H{sub_title_num_cell_row_num}"
1315
+ )
1316
+
1317
+ font_12_cells = []
1318
+ header_row_num = title_cell_row_num + 2
1319
+ category_header_cell = sheet.cell(header_row_num, 1)
1320
+ category_header_cell.value = _("Category (Storage List Sheet)")
1321
+
1322
+ ingredient_name_header_cell = sheet.cell(header_row_num, 2)
1323
+ ingredient_name_header_cell.value = _(
1324
+ "Ingredient Name (Storage List Sheet)"
1325
+ )
1326
+
1327
+ quantity_unit_name_header_cell = sheet.cell(header_row_num, 3)
1328
+ quantity_unit_name_header_cell.value = _(
1329
+ "Quantity Unit Name (Storage List Sheet)"
1330
+ )
1331
+
1332
+ quantity_header_cell = sheet.cell(header_row_num, 4)
1333
+ quantity_header_cell.value = _("Quantity (Storage List Sheet)")
1334
+
1335
+ unit_price_header_cell = sheet.cell(header_row_num, 5)
1336
+ unit_price_header_cell.value = _("Unit Price (Storage List Sheet)")
1337
+
1338
+ total_price_header_cell = sheet.cell(header_row_num, 6)
1339
+ total_price_header_cell.value = _(
1340
+ "Total Price (Storage List Sheet)"
1341
+ )
1342
+
1343
+ ingredients_total_price_header_cell = sheet.cell(header_row_num, 7)
1344
+ ingredients_total_price_header_cell.value = _(
1345
+ "Ingredients Total Price (Storage List Sheet)"
1346
+ )
1347
+
1348
+ note_header_cell = sheet.cell(header_row_num, 8)
1349
+ note_header_cell.value = _("Note (Storage List Sheet)")
1350
+
1351
+ font_12_cells += [
1352
+ category_header_cell,
1353
+ ingredient_name_header_cell,
1354
+ quantity_unit_name_header_cell,
1355
+ quantity_header_cell,
1356
+ unit_price_header_cell,
1357
+ total_price_header_cell,
1358
+ ingredients_total_price_header_cell,
1359
+ note_header_cell,
1360
+ ]
1361
+ for cell in font_12_cells:
1362
+ cell.alignment = self.center_alignment
1363
+ cell.font = self.font_12
1364
+ cell.border = self.thin_border
1365
+
1366
+ last_category = None
1367
+
1368
+ first_ingredient_row_num = header_row_num + 1
1369
+ for row_num in range(
1370
+ first_ingredient_row_num,
1371
+ first_ingredient_row_num + len(dated_ingredients) + 1,
1372
+ ):
1373
+ for col_num in range(1, 9):
1374
+ cell = sheet.cell(row_num, col_num)
1375
+ cell.font = self.font_12
1376
+ cell.alignment = self.center_alignment
1377
+ cell.border = self.thin_border
1378
+
1379
+ for index, ingredient in enumerate(dated_ingredients):
1380
+ ingredient_row_num = header_row_num + 1 + index
1381
+
1382
+ if not ingredient.category == last_category:
1383
+ sheet.cell(ingredient_row_num, 1, ingredient.category.name)
1384
+ sheet.cell(
1385
+ ingredient_row_num,
1386
+ 7,
1387
+ sum(
1388
+ [
1389
+ float(i.total_price)
1390
+ for i in dated_ingredients
1391
+ if i.category == ingredient.category
1392
+ ]
1393
+ )
1394
+ or "",
1395
+ )
1396
+ ingredients_same_category_len = len(
1397
+ [
1398
+ i
1399
+ for i in dated_ingredients
1400
+ if i.category == ingredient.category
1401
+ ]
1402
+ )
1403
+ sheet.merge_cells(
1404
+ f"A{ingredient_row_num}:A{ingredients_same_category_len+ingredient_row_num-1}"
1405
+ )
1406
+ sheet.merge_cells(
1407
+ f"G{ingredient_row_num}:G{ingredients_same_category_len+ingredient_row_num-1}"
1408
+ )
1409
+ last_category = ingredient.category
1410
+
1411
+ sheet.cell(ingredient_row_num, 2, ingredient.name)
1412
+ sheet.cell(ingredient_row_num, 3, ingredient.quantity_unit_name)
1413
+ sheet.cell(
1414
+ ingredient_row_num,
1415
+ 4,
1416
+ f"{ingredient.quantity:.2f}" if ingredient.quantity else "",
1417
+ )
1418
+ sheet.cell(
1419
+ ingredient_row_num,
1420
+ 5,
1421
+ (
1422
+ f"{ingredient.unit_price:.2f}"
1423
+ if ingredient.unit_price
1424
+ else ""
1425
+ ),
1426
+ )
1427
+ sheet.cell(
1428
+ ingredient_row_num,
1429
+ 6,
1430
+ (
1431
+ f"{ingredient.total_price:.2f}"
1432
+ if ingredient.total_price
1433
+ else ""
1434
+ ),
1435
+ )
1436
+ set_row_height_in_inches(
1437
+ sheet, ingredient_row_num, ingredient_row_height
1438
+ )
1439
+
1440
+ summary_total_price = sum(
1441
+ [float(i.total_price) for i in dated_ingredients]
1442
+ )
1443
+ summary_row_num = header_row_num + len(dated_ingredients) + 1
1444
+ sheet.cell(summary_row_num, 1, _("Summary (Storage List Sheet)"))
1445
+ sheet.cell(summary_row_num, 6, summary_total_price)
1446
+ sheet.cell(summary_row_num, 7, summary_total_price)
1447
+
1448
+ summary_row_height = ingredient_row_height
1449
+ set_row_height_in_inches(sheet, summary_row_num, summary_row_height)
1450
+
1451
+ signature_row_num = summary_row_num + 1
1452
+ signature_cell = sheet.cell(signature_row_num, 1)
1453
+ signature_cell.value = _(
1454
+ " Reviewer: Handler:{handler}   Weigher: Warehouseman:  "
1455
+ ).format(handler=user.username)
1456
+ signature_cell.font = self.font_14
1457
+ signature_cell.alignment = self.center_alignment
1458
+ sheet.merge_cells(f"A{signature_row_num}:H{signature_row_num}")
1459
+ set_row_height_in_inches(sheet, signature_row_num, 0.22)
1460
+
1461
+ for col_num, col_width in [
1462
+ [1, 1.13],
1463
+ [2, 1.98],
1464
+ [3, 0.85],
1465
+ [4, 0.85],
1466
+ [5, 0.88],
1467
+ [6, 1.28],
1468
+ [7, 1.19],
1469
+ [8, 0.78],
1470
+ ]:
1471
+ set_column_width_in_inches(sheet, col_num, col_width)
1472
+
1473
+ def fill_in_cover_sheet(self):
1474
+ sheet = self.cover_sheet
1475
+ user = self.user
1476
+ title_cell = sheet.cell(1, 1)
1477
+ title_cell.value = _(
1478
+ "Table of {affiliation} Canteen Ingredients Procurement Statistics in {month:0>2} {year}"
1479
+ ).format(
1480
+ affiliation=user.affiliation,
1481
+ year=self.year,
1482
+ month=self.month,
1483
+ )
1484
+ title_cell.font = self.font_16_bold
1485
+ title_cell.alignment = self.center_alignment
1486
+ for col_num, width in [
1487
+ [1, 1.96],
1488
+ [2, 2.26],
1489
+ [3, 4.44],
1490
+ ]:
1491
+ set_column_width_in_inches(sheet, col_num, width)
1492
+ sheet.merge_cells("A1:C1")
1493
+
1494
+ header_row_num = 2
1495
+ header_category_cell = sheet.cell(header_row_num, 1)
1496
+ header_category_cell.value = _("Ingredient Categories (cover sheet)")
1497
+
1498
+ header_total_price_cell = sheet.cell(header_row_num, 2)
1499
+ header_total_price_cell.value = _("Ingredient Total Prices")
1500
+
1501
+ header_note_cell = sheet.cell(header_row_num, 3)
1502
+ header_note_cell.value = _("Procurement Note")
1503
+
1504
+ for cell in [
1505
+ header_category_cell,
1506
+ header_total_price_cell,
1507
+ header_note_cell,
1508
+ ]:
1509
+ cell.alignment = self.center_alignment
1510
+ cell.border = self.thin_border
1511
+ cell.font = self.font_12_bold
1512
+
1513
+ categories = Category.objects.filter(
1514
+ Q(user=user) & Q(is_disabled=False)
1515
+ ).all()
1516
+
1517
+ set_row_height_in_inches(sheet, 1, 0.60)
1518
+ set_row_height_in_inches(sheet, 2, 0.44)
1519
+
1520
+ for index, category in enumerate(categories):
1521
+
1522
+ row_num = header_row_num + 1 + index
1523
+ set_row_height_in_inches(sheet, row_num, 0.44)
1524
+
1525
+ category_cell = sheet.cell(row_num, 1)
1526
+ category_cell.value = category.name
1527
+
1528
+ ingredients = Ingredient.objects.filter(
1529
+ Q(user=user)
1530
+ & Q(category=category)
1531
+ & Q(storage_date__gte=self.date_start)
1532
+ & Q(storage_date__lte=self.date_end)
1533
+ & Q(meal_type=self.meal_type)
1534
+ & Q(is_disabled=False)
1535
+ ).all()
1536
+ total_price_cell = sheet.cell(row_num, 2)
1537
+ total_price_cell.value = sum([i.total_price for i in ingredients])
1538
+
1539
+ note_cell = sheet.cell(row_num, 3)
1540
+ note_cell.value = _(
1541
+ "Total price of storaged ingredients is {0}, total price of non-storaged ingredients is {1}."
1542
+ ).format(
1543
+ sum([i.total_price for i in ingredients if not i.is_ignorable]),
1544
+ sum([i.total_price for i in ingredients if i.is_ignorable]),
1545
+ )
1546
+
1547
+ for cell in [category_cell, total_price_cell, note_cell]:
1548
+ cell.font = self.font_12
1549
+ cell.alignment = self.center_alignment
1550
+ cell.border = self.thin_border
1551
+
1552
+ ingredients = Ingredient.objects.filter(
1553
+ Q(user=user)
1554
+ & Q(storage_date__gte=self.date_start)
1555
+ & Q(storage_date__lte=self.date_end)
1556
+ & Q(meal_type=self.meal_type)
1557
+ & Q(is_disabled=False)
1558
+ ).all()
1559
+
1560
+ summary_row_num = len(categories) + header_row_num + 1
1561
+ summary_index_cell = sheet.cell(summary_row_num, 1)
1562
+ summary_index_cell.value = _("Procurement Summary")
1563
+
1564
+ summary_total_price_cell = sheet.cell(summary_row_num, 2)
1565
+ summary_total_price_cell.value = sum(
1566
+ [i.total_price for i in ingredients]
1567
+ )
1568
+
1569
+ summary_note_cell = sheet.cell(summary_row_num, 3)
1570
+ summary_note_cell.value = _(
1571
+ "Total price of storaged ingredients is {0}, total price of non-storaged ingredients is {1}."
1572
+ ).format(
1573
+ sum([i.total_price for i in ingredients if not i.is_ignorable]),
1574
+ sum([i.total_price for i in ingredients if i.is_ignorable]),
1575
+ )
1576
+
1577
+ for cell in [
1578
+ summary_index_cell,
1579
+ summary_total_price_cell,
1580
+ summary_note_cell,
1581
+ ]:
1582
+ cell.font = self.font_12_bold
1583
+ cell.alignment = self.center_alignment
1584
+ cell.border = self.thin_border
1585
+
1586
+ set_row_height_in_inches(sheet, summary_row_num, 0.44)
1587
+
1588
+ def fill_in_surplus_sheet(self):
1589
+ sheet = self.surplus_sheet
1590
+ user = self.user
1591
+ ingredient_rows_count = 17
1592
+
1593
+ ingredients = Ingredient.objects.filter(
1594
+ Q(user=user)
1595
+ & Q(storage_date__lte=self.date_end)
1596
+ & Q(meal_type=self.meal_type)
1597
+ & Q(is_disabled=False)
1598
+ & Q(is_ignorable=False)
1599
+ ).all()
1600
+ month_days = [
1601
+ self.date_start + timedelta(days=i)
1602
+ for i in range((self.date_end - self.date_start).days)
1603
+ ]
1604
+ month_days.append(self.date_end)
1605
+
1606
+ sundays = [d for d in month_days if d.weekday() == 6]
1607
+
1608
+ formed_ingredients = []
1609
+ for sunday in sundays:
1610
+ sunday_ingredients = []
1611
+ for ingredient in ingredients:
1612
+ remaining_quantity = ingredient.quantity - sum(
1613
+ [
1614
+ c.amount_used
1615
+ for c in ingredient.consumptions.filter(
1616
+ Q(is_disabled=False)
1617
+ ).all()
1618
+ if c.date_of_using <= sunday
1619
+ ]
1620
+ )
1621
+ if remaining_quantity > Decimal("0.0"):
1622
+ sunday_ingredients.append(ingredient)
1623
+ print(ingredient.name, remaining_quantity)
1624
+ form_count = len(sunday_ingredients) / ingredient_rows_count
1625
+ surplus_ingredients_len = (
1626
+ len(sunday_ingredients) % ingredient_rows_count
1627
+ )
1628
+ fake_ingredients_len = (
1629
+ ingredient_rows_count - surplus_ingredients_len
1630
+ )
1631
+ s_ingredient0 = sunday_ingredients[0]
1632
+ sunday_ingredients += [
1633
+ Ingredient(
1634
+ user=user,
1635
+ storage_date=self.date_start,
1636
+ name="",
1637
+ meal_type=self.meal_type,
1638
+ category=s_ingredient0.category,
1639
+ quantity=Decimal("0"),
1640
+ quantity_unit_name="",
1641
+ total_price=Decimal("0.0"),
1642
+ is_ignorable=False,
1643
+ is_disabled=False,
1644
+ )
1645
+ for i in range(fake_ingredients_len)
1646
+ ]
1647
+
1648
+ sunday_ingredients = sorted(
1649
+ sunday_ingredients, key=lambda i: i.category.name
1650
+ )
1651
+ for index in range(
1652
+ 0, len(sunday_ingredients), ingredient_rows_count
1653
+ ):
1654
+ split_ingredients = sunday_ingredients[
1655
+ index : index + ingredient_rows_count
1656
+ ]
1657
+
1658
+ formed_ingredients.append([sunday, index, split_ingredients])
1659
+
1660
+ for index, (sunday, sunday_index, ingredients) in enumerate(
1661
+ formed_ingredients
1662
+ ):
1663
+
1664
+ title_row_num = (ingredient_rows_count + 8) * index + 1
1665
+ title_cell = sheet.cell(title_row_num, 1)
1666
+ title_cell.value = _("Table of Surplus Ingredients")
1667
+ title_cell.font = self.font_20_bold
1668
+ title_cell.alignment = self.center_alignment
1669
+
1670
+ set_row_height_in_inches(sheet, title_row_num, 0.31)
1671
+ sheet.merge_cells(f"A{title_row_num}:I{title_row_num}")
1672
+
1673
+ sub_title_row_num = title_row_num + 1
1674
+ sub_title_affiliation_date_cell = sheet.cell(sub_title_row_num, 1)
1675
+ sub_title_affiliation_date_cell.value = (
1676
+ _(
1677
+ "Principal Name: {affiliation} {year}.{month:0>2}.{day:0>2} (Sub-title of Surplus Sheet)"
1678
+ )
1679
+ if self.is_school
1680
+ else _(
1681
+ "Affiliation Name: {affiliation} {year}.{month:0>2}.{day:0>2} (Sub-title of Surplus Sheet)"
1682
+ )
1683
+ ).format(
1684
+ affiliation=user.affiliation,
1685
+ year=sunday.year,
1686
+ month=sunday.month,
1687
+ day=sunday.day,
1688
+ )
1689
+
1690
+ sub_title_affiliation_date_cell.font = self.font_12
1691
+ sub_title_affiliation_date_cell.alignment = self.center_alignment
1692
+ sheet.merge_cells(f"A{sub_title_row_num}:I{sub_title_row_num}")
1693
+
1694
+ set_row_height_in_inches(sheet, sub_title_row_num, 0.20)
1695
+
1696
+ __style_row_end = sub_title_row_num + ingredient_rows_count + 5 + 1
1697
+ for row in range(sub_title_row_num, __style_row_end):
1698
+ set_row_height_in_inches(sheet, row, 0.20)
1699
+ for col in range(1, 10):
1700
+ cell = sheet.cell(row, col)
1701
+ cell.font = self.font_12
1702
+ cell.alignment = self.center_alignment
1703
+ if row < (__style_row_end - 1):
1704
+ cell.border = self.thin_border
1705
+
1706
+ header0_row_num = sub_title_row_num + 1
1707
+
1708
+ header0_ingredient_name_cell = sheet.cell(header0_row_num, 1)
1709
+ header0_ingredient_name_cell.value = _(
1710
+ "Ingredient Name (Surplus Sheet)"
1711
+ )
1712
+ sheet.merge_cells(f"A{header0_row_num}:A{header0_row_num+1}")
1713
+ header0_quantity_unit_name_cell = sheet.cell(header0_row_num, 2)
1714
+ header0_quantity_unit_name_cell.value = _(
1715
+ "Ingredient Quantity Unit Name (Surplus Sheet)"
1716
+ )
1717
+ sheet.merge_cells(f"B{header0_row_num}:B{header0_row_num+1}")
1718
+ header0_recorded_cell = sheet.cell(header0_row_num, 3)
1719
+ header0_recorded_cell.value = _("Recorded (Surplus Sheet)")
1720
+ sheet.merge_cells(f"C{header0_row_num}:D{header0_row_num}")
1721
+ header0_actual_cell = sheet.cell(header0_row_num, 5)
1722
+ header0_actual_cell.value = _("Actual (Surplus Sheet)")
1723
+ sheet.merge_cells(f"E{header0_row_num}:F{header0_row_num}")
1724
+ header0_difference_cell = sheet.cell(header0_row_num, 7)
1725
+ header0_difference_cell.value = _("Difference (Surplus Sheet)")
1726
+ sheet.merge_cells(f"G{header0_row_num}:H{header0_row_num}")
1727
+ header0_reason_cell = sheet.cell(header0_row_num, 9)
1728
+ header0_reason_cell.value = _("Reason (Surplus Sheet)")
1729
+ sheet.merge_cells(f"I{header0_row_num}:I{header0_row_num+1}")
1730
+
1731
+ header1_row_num = header0_row_num + 1
1732
+ header1_recorded_quantity_cell = sheet.cell(header1_row_num, 3)
1733
+ header1_recorded_quantity_cell.value = _(
1734
+ "Recorded Quantity (Surplus Sheet)"
1735
+ )
1736
+ header1_recorded_total_price_cell = sheet.cell(header1_row_num, 4)
1737
+ header1_recorded_total_price_cell.value = _(
1738
+ "Recorded Total Price (Surplus Sheet)"
1739
+ )
1740
+ header1_actual_quantity_cell = sheet.cell(header1_row_num, 5)
1741
+ header1_actual_quantity_cell.value = _(
1742
+ "Actual Quantity (Surplus Sheet)"
1743
+ )
1744
+ header1_actual_total_price_cell = sheet.cell(header1_row_num, 6)
1745
+ header1_actual_total_price_cell.value = _(
1746
+ "Actual Total Price (Surplus Sheet)"
1747
+ )
1748
+ header1_difference_quantity_cell = sheet.cell(header1_row_num, 7)
1749
+ header1_difference_quantity_cell.value = _(
1750
+ "Difference Quantity (Surplus Sheet)"
1751
+ )
1752
+ header1_difference_total_price_cell = sheet.cell(header1_row_num, 8)
1753
+ header1_difference_total_price_cell.value = _(
1754
+ "Difference Total Price (Surplus Sheet)"
1755
+ )
1756
+
1757
+ summary_total_price = Decimal("0.0")
1758
+ for index, ingredient in enumerate(ingredients):
1759
+ ingredient_row_num = header1_row_num + index + 1
1760
+ ingredient_quantity = (
1761
+ ingredient.quantity
1762
+ - sum(
1763
+ [
1764
+ c.amount_used
1765
+ for c in ingredient.consumptions.filter(
1766
+ is_disabled=False
1767
+ ).all()
1768
+ if c.date_of_using <= sunday
1769
+ ]
1770
+ )
1771
+ if ingredient.id
1772
+ else Decimal("0.0")
1773
+ )
1774
+ ingredient_total_price = (
1775
+ ingredient_quantity * ingredient.unit_price
1776
+ )
1777
+ summary_total_price += ingredient_total_price
1778
+
1779
+ sheet.cell(ingredient_row_num, 1, ingredient.name)
1780
+ sheet.cell(ingredient_row_num, 2, ingredient.quantity_unit_name)
1781
+ sheet.cell(ingredient_row_num, 3, ingredient_quantity)
1782
+ sheet.cell(ingredient_row_num, 4, ingredient_total_price)
1783
+ sheet.cell(ingredient_row_num, 5, ingredient_quantity)
1784
+ sheet.cell(ingredient_row_num, 6, ingredient_total_price)
1785
+ sheet.cell(ingredient_row_num, 7, "")
1786
+ sheet.cell(ingredient_row_num, 8, "")
1787
+ sheet.cell(ingredient_row_num, 9, "")
1788
+
1789
+ summary_row_num = header1_row_num + ingredient_rows_count + 1
1790
+
1791
+ prev_sunday = (
1792
+ formed_ingredients[index - 1][0]
1793
+ if 0 <= index - 1 < len(formed_ingredients)
1794
+ else None
1795
+ )
1796
+ next_sunday = (
1797
+ storaged_ingredients[index + 1][0]
1798
+ if 0 < index + 1 < (len(formed_ingredients) - 1)
1799
+ else None
1800
+ )
1801
+ summary_1_value = ""
1802
+
1803
+ if not next_sunday or not next_sunday == sunday:
1804
+ summary_1_value = _("Summary Total Price (Surplus Sheet)")
1805
+ summary_total_price = Decimal("0.0")
1806
+ ingredients_list = [
1807
+ __ingredients
1808
+ for __sunday, __sunday_index, __ingredients in formed_ingredients
1809
+ if __sunday == sunday
1810
+ ]
1811
+ for ingredients in ingredients_list:
1812
+ for ingredient in ingredients:
1813
+ ingredient_quantity = ingredient.quantity - (
1814
+ sum(
1815
+ [
1816
+ c.amount_used
1817
+ for c in ingredient.consumptions.filter(
1818
+ is_disabled=False
1819
+ ).all()
1820
+ ]
1821
+ )
1822
+ if ingredient.id
1823
+ else Decimal("0.0")
1824
+ )
1825
+ summary_total_price += (
1826
+ ingredient_quantity * ingredient.unit_price
1827
+ )
1828
+
1829
+ else:
1830
+ summary_1_value = _("Sub0-summary Total Price (Surplus Sheet)")
1831
+
1832
+ sheet.cell(summary_row_num, 1, summary_1_value)
1833
+
1834
+ sheet.cell(summary_row_num, 4, summary_total_price)
1835
+ sheet.cell(summary_row_num, 6, summary_total_price)
1836
+
1837
+ bottom_mote_row_num = summary_row_num + 1
1838
+ bottom_mote_cell = sheet.cell(bottom_mote_row_num, 1)
1839
+ bottom_mote_cell.value = _("Note: Inventory once a week.")
1840
+ sheet.merge_cells(f"A{bottom_mote_row_num}:I{bottom_mote_row_num}")
1841
+ signature_row_num = bottom_mote_row_num + 1
1842
+ signature_cell = sheet.cell(
1843
+ signature_row_num,
1844
+ 1,
1845
+ _(
1846
+ " Reviewer: Handler:{handler_name} Weigher: Warehouseman:   "
1847
+ ).format(handler_name=user.username),
1848
+ )
1849
+ sheet.merge_cells(f"A{signature_row_num}:I{signature_row_num}")
1850
+
1851
+ for col, width in [
1852
+ [1, 2.08],
1853
+ [2, 0.49],
1854
+ [3, 0.75],
1855
+ [4, 1.14],
1856
+ [5, 0.89],
1857
+ [6, 1.15],
1858
+ [7, 0.74],
1859
+ [8, 0.74],
1860
+ [9, 0.56],
1861
+ ]:
1862
+ set_column_width_in_inches(sheet, col, width)
1863
+
1864
+ def fill_in_non_storage_list_sheet(self):
1865
+ sheet = self.non_storage_list_sheet
1866
+ user = self.user
1867
+ ingredient_rows_count = 11
1868
+
1869
+ ingredients = Ingredient.objects.filter(
1870
+ Q(user=user)
1871
+ & Q(storage_date__gte=self.date_start)
1872
+ & Q(storage_date__lte=self.date_end)
1873
+ & Q(meal_type=self.meal_type)
1874
+ & Q(is_disabled=False)
1875
+ & Q(is_ignorable=True)
1876
+ ).all()
1877
+
1878
+ categories = list(set([i.category for i in ingredients]))
1879
+
1880
+ category_ingredients = []
1881
+ for category in categories:
1882
+ _ingredients = [i for i in ingredients if i.category == category]
1883
+ split_count = math.ceil(len(_ingredients) / ingredient_rows_count)
1884
+ for i in range(0, len(_ingredients), ingredient_rows_count):
1885
+ _split_ingredients = _ingredients[i : i + ingredient_rows_count]
1886
+ _split_ingredient0 = _split_ingredients[0]
1887
+ if len(_split_ingredients) < ingredient_rows_count:
1888
+ _split_ingredients += [
1889
+ Ingredient(
1890
+ user=user,
1891
+ storage_date=None,
1892
+ name="",
1893
+ meal_type=_split_ingredient0.meal_type,
1894
+ category=_split_ingredient0.category,
1895
+ quantity=Decimal("0"),
1896
+ quantity_unit_name=None,
1897
+ total_price=Decimal("0"),
1898
+ is_ignorable=_split_ingredient0.is_ignorable,
1899
+ )
1900
+ for i in range(
1901
+ ingredient_rows_count - len(_split_ingredients)
1902
+ )
1903
+ ]
1904
+ category_ingredients.append([category, _split_ingredients])
1905
+
1906
+ for index, (category, c_ingredients) in enumerate(category_ingredients):
1907
+ row_num = (ingredient_rows_count + 5) * index + 1
1908
+ title_row_num = row_num
1909
+ title_cell = sheet.cell(title_row_num, 1)
1910
+ title_cell.value = _(
1911
+ "Table of Non-storaged Ingredients ({category})"
1912
+ ).format(category=category)
1913
+ title_cell.font = self.font_20_bold
1914
+ title_cell.alignment = self.center_alignment
1915
+
1916
+ set_row_height_in_inches(sheet, title_row_num, 0.42)
1917
+ sheet.merge_cells(f"A{title_row_num}:G{title_row_num}")
1918
+
1919
+ sub_title_row_num = title_row_num + 1
1920
+ sub_title_affiliation_cell = sheet.cell(sub_title_row_num, 1)
1921
+ sub_title_affiliation_cell.value = (
1922
+ _("Principal Name: {affiliation}")
1923
+ if self.is_school
1924
+ else _("Affiliation Name: {affiliation}")
1925
+ ).format(affiliation=user.affiliation)
1926
+
1927
+ sub_title_date_cell = sheet.cell(sub_title_row_num, 4)
1928
+ sub_title_date_cell.value = _(
1929
+ "{year}.{month:0>2}.{day:0>2} (Non-storage list sheet)"
1930
+ ).format(year=self.year, month=self.month, day=self.date_end.day)
1931
+
1932
+ for cell in [sub_title_affiliation_cell, sub_title_date_cell]:
1933
+ cell.font = self.font_14
1934
+ cell.alignment = self.center_alignment
1935
+
1936
+ sheet.merge_cells(f"A{sub_title_row_num}:C{sub_title_row_num}")
1937
+ sheet.merge_cells(f"D{sub_title_row_num}:G{sub_title_row_num}")
1938
+
1939
+ set_row_height_in_inches(sheet, sub_title_row_num, 0.33)
1940
+
1941
+ header_row_num = sub_title_row_num + 1
1942
+ for col, value in [
1943
+ [1, _("Procurement Date (Non-storage list sheet)")],
1944
+ [2, _("Ingredient Name (Non-storage list sheet)")],
1945
+ [3, _("Unit Name of Quantity (Non-storage list sheet)")],
1946
+ [4, _("Quantity (Non-storage list sheet)")],
1947
+ [5, _("Unit Price (Non-storage list sheet)")],
1948
+ [6, _("Total Price (Non-storage list sheet)")],
1949
+ [7, _("Note (Non-storage list sheet)")],
1950
+ ]:
1951
+ cell = sheet.cell(header_row_num, col)
1952
+ cell.value = value
1953
+ cell.font = self.font_16
1954
+ cell.alignment = self.center_alignment
1955
+ cell.border = self.thin_border
1956
+
1957
+ set_row_height_in_inches(sheet, header_row_num, 0.30)
1958
+
1959
+ for i_index, ingredient in enumerate(c_ingredients):
1960
+ ingredient_row_num = header_row_num + 1 + i_index
1961
+
1962
+ storage_date_cell = sheet.cell(ingredient_row_num, 1)
1963
+ storage_date_cell.value = (
1964
+ _(
1965
+ "{year}.{month:0>2}.{day:0>2} (Column of Non-storage list sheet)"
1966
+ ).format(
1967
+ year=ingredient.storage_date.year,
1968
+ month=ingredient.storage_date.month,
1969
+ day=ingredient.storage_date.day,
1970
+ )
1971
+ if ingredient.storage_date
1972
+ else ""
1973
+ )
1974
+ name_cell = sheet.cell(ingredient_row_num, 2)
1975
+ name_cell.value = ingredient.name
1976
+ quantity_unit_name_cell = sheet.cell(ingredient_row_num, 3)
1977
+ quantity_unit_name_cell.value = ingredient.quantity_unit_name
1978
+ quantity_cell = sheet.cell(ingredient_row_num, 4)
1979
+ quantity_cell.value = (
1980
+ ingredient.quantity if ingredient.quantity else ""
1981
+ )
1982
+ unit_price_cell = sheet.cell(ingredient_row_num, 5)
1983
+ unit_price_cell.value = (
1984
+ ingredient.unit_price if ingredient.unit_price else ""
1985
+ )
1986
+ total_price_cell = sheet.cell(ingredient_row_num, 6)
1987
+ total_price_cell.value = (
1988
+ ingredient.total_price if ingredient.total_price else ""
1989
+ )
1990
+ note_cell = sheet.cell(ingredient_row_num, 7)
1991
+ note_cell.value = ""
1992
+
1993
+ for col in range(1, 8):
1994
+ cell = sheet.cell(ingredient_row_num, col)
1995
+ cell.font = self.font_12
1996
+ cell.alignment = self.center_alignment
1997
+ cell.border = self.thin_border
1998
+
1999
+ set_row_height_in_inches(sheet, ingredient_row_num, 0.30)
2000
+
2001
+ summary_row_num = header_row_num + ingredient_rows_count + 1
2002
+
2003
+ next_category, ___ = (
2004
+ category_ingredients[index + 1]
2005
+ if (index + 1) < len(category_ingredients)
2006
+ else (None, None)
2007
+ )
2008
+ summary_note_cell = sheet.cell(summary_row_num, 2)
2009
+ summary_total_price_cell = sheet.cell(summary_row_num, 6)
2010
+
2011
+ c_total_price = Decimal("0.0")
2012
+ if (not next_category) or (next_category != category):
2013
+ summary_note_cell.value = _("Summary (Non-storage list sheet)")
2014
+ c_ingredients_list = [
2015
+ _c_ingredients
2016
+ for _category, _c_ingredients in category_ingredients
2017
+ if _category == category
2018
+ ]
2019
+ c_total_price = Decimal("0.0")
2020
+ for _c_ingredients in c_ingredients_list:
2021
+ c_total_price += sum(
2022
+ [i.total_price for i in _c_ingredients]
2023
+ )
2024
+ summary_total_price_cell.value = (
2025
+ f"{c_total_price:.{decimal_prec}f}"
2026
+ )
2027
+
2028
+ else:
2029
+ summary_note_cell.value = _(
2030
+ "Sub-summary (Non-storage list sheet)"
2031
+ )
2032
+ c_total_price += sum([i.total_price for i in c_ingredients])
2033
+ summary_total_price_cell.value = (
2034
+ f"{c_total_price:.{decimal_prec}f}"
2035
+ )
2036
+
2037
+ for col in range(1, 8):
2038
+ cell = sheet.cell(summary_row_num, col)
2039
+ cell.font = self.font_14
2040
+ cell.alignment = self.center_alignment
2041
+ cell.border = self.thin_border
2042
+
2043
+ set_row_height_in_inches(sheet, summary_row_num, 0.30)
2044
+
2045
+ for col, width in [
2046
+ [1, 1.17],
2047
+ [2, 1.67],
2048
+ [3, 1.30],
2049
+ [4, 0.86],
2050
+ [5, 0.95],
2051
+ [6, 1.28],
2052
+ [7, 1.04],
2053
+ ]:
2054
+ set_column_width_in_inches(sheet, col, width)
2055
+
2056
+ def fill_in(self):
2057
+ self.fill_in_cover_sheet()
2058
+ self.fill_in_storage_sheet()
2059
+ self.fill_in_storage_list_sheet()
2060
+ self.fill_in_non_storage_sheet()
2061
+ self.fill_in_non_storage_list_sheet()
2062
+ self.fill_in_consumption_sheet()
2063
+ self.fill_in_consumption_list_sheet()
2064
+ self.fill_in_surplus_sheet()
2065
+ # self.fill_in_food_sheets()
2066
+
2067
+ return self.wb
2068
+
2069
+
2070
+ def get_workbook_zip(request, month):
2071
+ meal_types = MealType.objects.filter(
2072
+ Q(user=request.user) & Q(is_disabled=False)
2073
+ )
2074
+
2075
+ zip_buffer = io.BytesIO()
2076
+ with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file:
2077
+ for meal_type in meal_types:
2078
+ filename = (
2079
+ _(
2080
+ "Canteen {meal_type} Daybook WorkBook ({month}) of {affiliation}"
2081
+ ).format(
2082
+ meal_type=meal_type.abbreviation or meal_type.name,
2083
+ month=month.replace("-", ""),
2084
+ affiliation=request.user.affiliation,
2085
+ )
2086
+ + ".xlsx"
2087
+ )
2088
+ # filename = escape_uri_path(filename)
2089
+
2090
+ wb = CanteenWorkBook(request, month, meal_type).fill_in()
2091
+ excel_buffer = io.BytesIO()
2092
+ wb.save(excel_buffer)
2093
+ excel_buffer.seek(0)
2094
+
2095
+ zip_file.writestr(filename, excel_buffer.getvalue())
2096
+
2097
+ zip_buffer.seek(0)
2098
+ return zip_buffer