Compare commits

...

3 Commits
stable ... main

Author SHA1 Message Date
01f8d2114e fix bugs 2025-08-19 19:06:26 +07:00
80a514f1de fix many bugs 2025-08-19 12:28:49 +07:00
a3ce1a8843 add git ignore file 2025-08-18 16:37:23 +07:00
80 changed files with 51451 additions and 47217 deletions

65
.gitignore vendored Normal file
View File

@ -0,0 +1,65 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
*.exe
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# PyBuilder
target/
# PEP 582; used by python-next
__pypackages__/
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# VS Code
.vscode/

View File

@ -8,6 +8,7 @@ A comprehensive Django-based manufacturing management application with modern UI
- **Simple Manufacturing Orders**: Input end product results without complex BOM requirements
- **Production Tracking**: Monitor daily, weekly, and monthly production metrics
- **Cost Management**: Track labor and overhead costs for manufacturing orders
- **Edit/Delete Orders**: Admin/superuser can edit or delete manufacturing orders
### 📦 Inventory Management
- **Product Management**: Complete product catalog with categories, pricing, and stock levels
@ -188,6 +189,12 @@ basic_manufacture_app/
3. Add labor and overhead costs
4. Save the order (automatically updates inventory)
### Editing a Manufacturing Order
1. Navigate to Manufacturing → Order List
2. Click on the order to view details
3. Click "Edit" button (admin/superuser only)
4. Modify order details and save
### Managing Inventory
1. Go to Inventory → Products
2. Add new products with categories and pricing

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -452,6 +452,18 @@
('core.apps',
'D:\\Pythoncode\\Django_Basic_Manufacturing\\core\\apps.py',
'PYMODULE'),
('core.management',
'D:\\Pythoncode\\Django_Basic_Manufacturing\\core\\management\\__init__.py',
'PYMODULE'),
('core.management.commands',
'D:\\Pythoncode\\Django_Basic_Manufacturing\\core\\management\\commands\\__init__.py',
'PYMODULE'),
('core.management.commands.check_database',
'D:\\Pythoncode\\Django_Basic_Manufacturing\\core\\management\\commands\\check_database.py',
'PYMODULE'),
('core.management.commands.populate_test_data',
'D:\\Pythoncode\\Django_Basic_Manufacturing\\core\\management\\commands\\populate_test_data.py',
'PYMODULE'),
('core.urls',
'D:\\Pythoncode\\Django_Basic_Manufacturing\\core\\urls.py',
'PYMODULE'),
@ -6098,6 +6110,9 @@
('users.forms',
'D:\\Pythoncode\\Django_Basic_Manufacturing\\users\\forms.py',
'PYMODULE'),
('users.management',
'D:\\Pythoncode\\Django_Basic_Manufacturing\\users\\management\\__init__.py',
'PYMODULE'),
('users.migrations',
'D:\\Pythoncode\\Django_Basic_Manufacturing\\users\\migrations\\__init__.py',
'PYMODULE'),

Binary file not shown.

Binary file not shown.

View File

@ -14,9 +14,12 @@ Types if import:
IMPORTANT: Do NOT post this list to the issue-tracker. Use it as a basis for
tracking down the missing module yourself. Thanks!
missing module named pyimod02_importers - imported by D:\Pythoncode\Django_Basic_Manufacturing\.venv\Lib\site-packages\PyInstaller\hooks\rthooks\pyi_rth_pkgutil.py (delayed), D:\Pythoncode\Django_Basic_Manufacturing\.venv\Lib\site-packages\PyInstaller\hooks\rthooks\pyi_rth_pkgres.py (delayed)
missing module named pwd - imported by posixpath (delayed, conditional, optional), shutil (delayed, optional), tarfile (optional), pathlib (delayed, optional), subprocess (delayed, conditional, optional), getpass (delayed), http.server (delayed, optional), webbrowser (delayed), netrc (delayed, conditional), smtpd (conditional, optional), distutils.util (delayed, conditional, optional), distutils.archive_util (optional), setuptools._distutils.archive_util (optional), setuptools._distutils.util (delayed, conditional, optional)
missing module named grp - imported by shutil (delayed, optional), tarfile (optional), pathlib (delayed, optional), subprocess (delayed, conditional, optional), distutils.archive_util (optional), setuptools._distutils.archive_util (optional)
missing module named _posixsubprocess - imported by subprocess (conditional), multiprocessing.util (delayed)
missing module named fcntl - imported by subprocess (optional), django.core.files.locks (conditional, optional), pty (delayed, optional)
missing module named 'org.python' - imported by copy (optional), xml.sax (delayed, conditional)
missing module named _frozen_importlib_external - imported by importlib._bootstrap (delayed), importlib (optional), importlib.abc (optional), zipimport (top-level), pip._vendor.distlib.resources (optional)
excluded module named _frozen_importlib - imported by importlib (optional), importlib.abc (optional), zipimport (top-level), pip._vendor.distlib.resources (optional)
missing module named org - imported by pickle (optional)
missing module named urllib.splittype - imported by urllib (conditional), pip._vendor.distlib.compat (conditional)
missing module named urllib.ContentTooShortError - imported by urllib (conditional), pip._vendor.distlib.compat (conditional)
@ -25,18 +28,15 @@ missing module named urllib.url2pathname - imported by urllib (conditional), pip
missing module named urllib.unquote - imported by urllib (conditional), pip._vendor.distlib.compat (conditional)
missing module named urllib.quote - imported by urllib (conditional), pip._vendor.distlib.compat (conditional)
missing module named urllib.urlretrieve - imported by urllib (conditional), pip._vendor.distlib.compat (conditional)
missing module named posix - imported by os (conditional, optional), posixpath (optional), shutil (conditional), importlib._bootstrap_external (conditional)
missing module named posix - imported by posixpath (optional), shutil (conditional), importlib._bootstrap_external (conditional), os (conditional, optional)
missing module named resource - imported by posix (top-level), test.support (delayed, conditional, optional)
missing module named grp - imported by shutil (delayed, optional), tarfile (optional), pathlib (delayed, optional), subprocess (delayed, conditional, optional), distutils.archive_util (optional), setuptools._distutils.archive_util (optional)
missing module named pwd - imported by posixpath (delayed, conditional, optional), shutil (delayed, optional), tarfile (optional), pathlib (delayed, optional), subprocess (delayed, conditional, optional), getpass (delayed), http.server (delayed, optional), webbrowser (delayed), netrc (delayed, conditional), smtpd (conditional, optional), distutils.util (delayed, conditional, optional), distutils.archive_util (optional), setuptools._distutils.archive_util (optional), setuptools._distutils.util (delayed, conditional, optional)
missing module named pyimod02_importers - imported by D:\Pythoncode\Django_Basic_Manufacturing\.venv\Lib\site-packages\PyInstaller\hooks\rthooks\pyi_rth_pkgutil.py (delayed), D:\Pythoncode\Django_Basic_Manufacturing\.venv\Lib\site-packages\PyInstaller\hooks\rthooks\pyi_rth_pkgres.py (delayed)
missing module named _posixsubprocess - imported by subprocess (conditional), multiprocessing.util (delayed)
missing module named fcntl - imported by subprocess (optional), django.core.files.locks (conditional, optional), pty (delayed, optional)
missing module named _manylinux - imported by pip._vendor.packaging._manylinux (delayed, optional), packaging._manylinux (delayed, optional), setuptools._vendor.packaging._manylinux (delayed, optional), pkg_resources._vendor.packaging._manylinux (delayed, optional)
missing module named jinja2 - imported by django.template.backends.jinja2 (top-level), django.test.utils (optional), pkg_resources._vendor.pyparsing.diagram (top-level), setuptools._vendor.pyparsing.diagram (top-level)
missing module named pyparsing - imported by pkg_resources._vendor.pyparsing.diagram (top-level), setuptools._vendor.pyparsing.diagram (top-level)
missing module named railroad - imported by pkg_resources._vendor.pyparsing.diagram (top-level), setuptools._vendor.pyparsing.diagram (top-level)
missing module named termios - imported by django.utils.autoreload (optional), getpass (optional), tty (top-level)
missing module named _frozen_importlib_external - imported by importlib._bootstrap (delayed), importlib (optional), importlib.abc (optional), zipimport (top-level), pip._vendor.distlib.resources (optional)
excluded module named _frozen_importlib - imported by importlib (optional), importlib.abc (optional), zipimport (top-level), pip._vendor.distlib.resources (optional)
missing module named readline - imported by code (delayed, conditional, optional), django.core.management.commands.shell (delayed, optional), rlcompleter (optional), cmd (delayed, conditional, optional), pdb (delayed, optional), site (delayed, optional), pstats (conditional, optional), django_extensions.management.commands.shell_plus (delayed, optional)
missing module named 'pkg_resources.extern.pyparsing' - imported by pkg_resources._vendor.packaging.markers (top-level), pkg_resources._vendor.packaging.requirements (top-level)
missing module named 'pkg_resources.extern.importlib_resources' - imported by pkg_resources._vendor.jaraco.text (optional)
@ -90,6 +90,16 @@ missing module named defusedxml - imported by PIL.Image (optional)
missing module named pytz - imported by django.utils.timezone (delayed, conditional), django.db.backends.base.base (delayed, conditional), django.templatetags.tz (delayed, conditional)
missing module named _typeshed - imported by asgiref.sync (conditional), pip._vendor.pkg_resources (conditional)
missing module named backports - imported by django.utils.timezone (optional), django.db.backends.base.base (optional), django.templatetags.tz (optional)
missing module named colorama - imported by django.core.management.color (optional)
missing module named win32evtlog - imported by logging.handlers (delayed, optional)
missing module named win32evtlogutil - imported by logging.handlers (delayed, optional)
missing module named redis - imported by django.core.cache.backends.redis (delayed), pip._vendor.cachecontrol.caches.redis_cache (conditional)
missing module named pymemcache - imported by django.core.cache.backends.memcached (delayed)
missing module named pylibmc - imported by django.core.cache.backends.memcached (delayed)
missing module named django.db.models.BooleanField - imported by django.db.models (delayed), django.db.models.query_utils (delayed), django.db.models.sql.where (delayed), django.contrib.gis.db.models.functions (top-level), django.contrib.postgres.aggregates.general (top-level), django_extensions.management.commands.dumpscript (top-level)
missing module named django.db.models.DurationField - imported by django.db.models (top-level), django.db.backends.oracle.functions (top-level)
missing module named django.db.models.DecimalField - imported by django.db.models (top-level), django.db.backends.oracle.functions (top-level)
missing module named django.db.models.NOT_PROVIDED - imported by django.db.models (top-level), django.db.models.base (top-level), django.db.migrations.operations.fields (top-level), django.db.migrations.state (top-level), django.db.migrations.questioner (top-level), django.db.backends.mysql.schema (top-level)
missing module named 'psycopg.types' - imported by django.db.backends.postgresql.psycopg_any (optional), django.db.backends.postgresql.operations (conditional), django.contrib.gis.db.backends.postgis.base (conditional), django.contrib.postgres.signals (conditional)
missing module named 'psycopg.pq' - imported by django.db.backends.postgresql.base (conditional), django.contrib.gis.db.backends.postgis.base (conditional)
missing module named 'psycopg.postgres' - imported by django.db.backends.postgresql.psycopg_any (optional)
@ -98,34 +108,27 @@ missing module named cx_Oracle - imported by django.db.backends.oracle.base (opt
missing module named 'MySQLdb.converters' - imported by django.db.backends.mysql.base (top-level)
missing module named 'MySQLdb.constants' - imported by django.db.backends.mysql.base (top-level), django.db.backends.mysql.introspection (top-level), django.contrib.gis.db.backends.mysql.introspection (top-level)
missing module named MySQLdb - imported by django.db.backends.mysql.base (optional), django_extensions.management.commands.drop_test_database (delayed, conditional), django_extensions.management.commands.reset_db (delayed, conditional)
missing module named colorama - imported by django.core.management.color (optional)
missing module named win32evtlog - imported by logging.handlers (delayed, optional)
missing module named win32evtlogutil - imported by logging.handlers (delayed, optional)
missing module named redis - imported by django.core.cache.backends.redis (delayed), pip._vendor.cachecontrol.caches.redis_cache (conditional)
missing module named pymemcache - imported by django.core.cache.backends.memcached (delayed)
missing module named pylibmc - imported by django.core.cache.backends.memcached (delayed)
missing module named django.db.models.Max - imported by django.db.models (top-level), django.db.models.base (top-level)
missing module named django.db.models.IntegerField - imported by django.db.models (top-level), django.db.models.base (top-level), django.contrib.gis.db.models.functions (top-level), django.contrib.postgres.fields.array (top-level), django.contrib.postgres.aggregates.statistics (top-level)
missing module named django.db.models.NOT_PROVIDED - imported by django.db.models (top-level), django.db.models.base (top-level), django.db.migrations.operations.fields (top-level), django.db.migrations.state (top-level), django.db.migrations.questioner (top-level), django.db.backends.mysql.schema (top-level)
missing module named django.db.models.Field - imported by django.db.models (top-level), django.db.models.query (top-level), django.forms.models (delayed), django.contrib.admin.views.main (top-level), django.contrib.gis.db.models.fields (top-level), django.contrib.postgres.search (top-level), django.contrib.postgres.fields.array (top-level), django.contrib.postgres.fields.hstore (top-level)
missing module named django.db.models.DateTimeField - imported by django.db.models (top-level), django.db.models.query (top-level), django.contrib.postgres.functions (top-level), django_extensions.db.fields (top-level), django_extensions.management.commands.dumpscript (top-level)
missing module named django.db.models.DateField - imported by django.db.models (top-level), django.db.models.query (top-level), django_extensions.management.commands.dumpscript (top-level)
missing module named django.db.models.DurationField - imported by django.db.models (top-level), django.db.backends.oracle.functions (top-level)
missing module named django.db.models.DecimalField - imported by django.db.models (top-level), django.db.backends.oracle.functions (top-level)
missing module named django.db.models.BooleanField - imported by django.db.models (delayed), django.db.models.query_utils (delayed), django.db.models.sql.where (delayed), django.contrib.gis.db.models.functions (top-level), django.contrib.postgres.aggregates.general (top-level), django_extensions.management.commands.dumpscript (top-level)
missing module named django.db.models.UniqueConstraint - imported by django.db.models (top-level), django.db.models.options (top-level), django.db.backends.mysql.schema (top-level), django.db.backends.sqlite3.schema (top-level), django_extensions.db.fields (top-level), django_extensions.management.commands.sqldiff (top-level)
missing module named django.db.models.AutoField - imported by django.db.models (top-level), django.db.models.options (top-level), django.db.models.query (top-level), django.forms.models (delayed), django.db.backends.oracle.operations (top-level), django_extensions.management.commands.dumpscript (top-level)
missing module named pywatchman - imported by django.utils.autoreload (optional)
missing module named django.db.models.Field - imported by django.db.models (top-level), django.db.models.query (top-level), django.forms.models (delayed), django.contrib.admin.views.main (top-level), django.contrib.gis.db.models.fields (top-level), django.contrib.postgres.search (top-level), django.contrib.postgres.fields.array (top-level), django.contrib.postgres.fields.hstore (top-level)
missing module named django.db.models.DateTimeField - imported by django.db.models (top-level), django.db.models.query (top-level), django.contrib.postgres.functions (top-level), django_extensions.db.fields (top-level), django_extensions.management.commands.dumpscript (top-level)
missing module named django.db.models.DateField - imported by django.db.models (top-level), django.db.models.query (top-level), django_extensions.management.commands.dumpscript (top-level)
missing module named django.db.models.Max - imported by django.db.models (top-level), django.db.models.base (top-level)
missing module named django.db.models.IntegerField - imported by django.db.models (top-level), django.db.models.base (top-level), django.contrib.gis.db.models.functions (top-level), django.contrib.postgres.fields.array (top-level), django.contrib.postgres.aggregates.statistics (top-level)
missing module named collections.MutableMapping - imported by collections (optional), pip._vendor.urllib3._collections (optional), pip._vendor.distlib.compat (optional)
missing module named collections.Mapping - imported by collections (optional), pip._vendor.urllib3._collections (optional)
missing module named tblib - imported by django.test.runner (optional)
missing module named ipdb - imported by django.test.runner (optional), django_extensions.management.utils (delayed, optional), django_extensions.management.commands.runserver_plus (delayed, conditional, optional)
missing module named bpython - imported by django.core.management.commands.shell (delayed), django_extensions.management.commands.shell_plus (delayed, optional)
missing module named ConfigParser - imported by pip._vendor.distlib.compat (conditional), decouple (conditional)
missing module named 'pygments.lexers' - imported by django_extensions.templatetags.highlighting (optional), django_extensions.templatetags.syntax_color (optional)
missing module named 'pygments.formatters' - imported by django_extensions.management.debug_cursor (delayed, conditional, optional), django_extensions.templatetags.highlighting (optional), django_extensions.templatetags.syntax_color (optional)
missing module named pygments - imported by django_extensions.management.debug_cursor (delayed, conditional, optional), django_extensions.templatetags.highlighting (optional), django_extensions.templatetags.syntax_color (optional)
missing module named 'mongoengine.queryset' - imported by django_extensions.mongodb.models (top-level)
missing module named 'mongoengine.fields' - imported by django_extensions.mongodb.models (top-level), django_extensions.mongodb.fields (top-level)
missing module named 'mongoengine.document' - imported by django_extensions.mongodb.models (top-level)
missing module named IPython - imported by django_extensions.management.utils (delayed, optional), django_extensions.management.commands.shell_plus (delayed, optional)
missing module named ipdb - imported by django.test.runner (optional), django_extensions.management.utils (delayed, optional), django_extensions.management.commands.runserver_plus (delayed, conditional, optional)
missing module named mongoengine - imported by django_extensions.management.shells (delayed, optional)
missing module named 'pygments.formatters' - imported by django_extensions.templatetags.syntax_color (optional), django_extensions.templatetags.highlighting (optional), django_extensions.management.debug_cursor (delayed, conditional, optional)
missing module named 'pygments.lexers' - imported by django_extensions.templatetags.syntax_color (optional), django_extensions.templatetags.highlighting (optional), django_extensions.management.debug_cursor (delayed, conditional, optional)
missing module named boto - imported by django_extensions.management.commands.sync_s3 (optional)
missing module named 'prompt_toolkit.contrib' - imported by django_extensions.management.commands.shell_plus (delayed, optional)
missing module named 'ptpython.ipython' - imported by django_extensions.management.commands.shell_plus (delayed, optional)
@ -133,7 +136,6 @@ missing module named 'ptpython.repl' - imported by django_extensions.management.
missing module named prompt_toolkit - imported by django_extensions.management.commands.shell_plus (delayed, optional)
missing module named ptpython - imported by django_extensions.management.commands.shell_plus (delayed, optional)
missing module named 'IPython.Shell' - imported by django_extensions.management.commands.shell_plus (delayed, optional)
missing module named bpython - imported by django.core.management.commands.shell (delayed), django_extensions.management.commands.shell_plus (delayed, optional)
missing module named 'notebook.notebookapp' - imported by django_extensions.management.commands.shell_plus (delayed, optional)
missing module named jupyterlab - imported by django_extensions.management.commands.shell_plus (delayed, optional)
missing module named 'IPython.frontend' - imported by django_extensions.management.commands.shell_plus (delayed, optional)
@ -161,7 +163,6 @@ missing module named 'pip._vendor.rich.markdown' - imported by pip._vendor.rich.
missing module named _abcoll - imported by pip._vendor.distlib.compat (optional)
missing module named dummy_thread - imported by pip._vendor.distlib.compat (optional)
missing module named thread - imported by pip._vendor.distlib.compat (optional)
missing module named collections.MutableMapping - imported by collections (optional), pip._vendor.urllib3._collections (optional), pip._vendor.distlib.compat (optional)
missing module named htmlentitydefs - imported by pip._vendor.distlib.compat (conditional)
missing module named HTMLParser - imported by pip._vendor.distlib.compat (conditional)
missing module named Queue - imported by pip._vendor.urllib3.util.queue (conditional), pip._vendor.distlib.compat (conditional)
@ -179,7 +180,6 @@ missing module named 'cryptography.x509' - imported by pip._vendor.urllib3.contr
missing module named 'cryptography.hazmat' - imported by pip._vendor.urllib3.contrib.pyopenssl (top-level)
missing module named cryptography - imported by pip._vendor.urllib3.contrib.pyopenssl (top-level), pip._vendor.requests (conditional, optional)
missing module named 'OpenSSL.SSL' - imported by pip._vendor.urllib3.contrib.pyopenssl (top-level)
missing module named collections.Mapping - imported by collections (optional), pip._vendor.urllib3._collections (optional)
missing module named socks - imported by pip._vendor.urllib3.contrib.socks (optional)
missing module named pip.__file__ - imported by pip (top-level), pip._internal.build_env (top-level)
missing module named filelock - imported by pip._vendor.cachecontrol.caches.file_cache (delayed, conditional, optional)
@ -195,8 +195,8 @@ missing module named pydotplus - imported by django_extensions.management.comman
missing module named pygraphviz - imported by django_extensions.management.commands.graph_models (optional)
missing module named vobject - imported by django_extensions.management.commands.export_emails (delayed, optional)
missing module named shortuuid - imported by django_extensions.db.fields (optional)
missing module named pygments - imported by django_extensions.templatetags.syntax_color (optional), django_extensions.templatetags.highlighting (optional)
missing module named selenium - imported by django.test.selenium (delayed, conditional)
missing module named tblib - imported by django.test.runner (optional)
missing module named django.contrib.postgres.fields.ArrayField - imported by django.contrib.postgres.fields (top-level), django.contrib.postgres.expressions (top-level), django.contrib.postgres.aggregates.general (top-level)
missing module named 'geoip2.database' - imported by django.contrib.gis.geoip2.base (top-level)
missing module named geoip2 - imported by django.contrib.gis.geoip2 (optional)

View File

@ -156,6 +156,8 @@ imports:
&#8226; <a href="#core">core</a>
&#8226; <a href="#crispy_bootstrap5">crispy_bootstrap5</a>
&#8226; <a href="#crispy_forms">crispy_forms</a>
&#8226; <a href="#django">django</a>
&#8226; <a href="#django.conf">django.conf</a>
&#8226; <a href="#django.contrib.admin">django.contrib.admin</a>
&#8226; <a href="#django.contrib.admin.apps">django.contrib.admin.apps</a>
&#8226; <a href="#django.contrib.auth">django.contrib.auth</a>
@ -169,6 +171,17 @@ imports:
&#8226; <a href="#django.contrib.staticfiles">django.contrib.staticfiles</a>
&#8226; <a href="#django.contrib.staticfiles.apps">django.contrib.staticfiles.apps</a>
&#8226; <a href="#django.core.management">django.core.management</a>
&#8226; <a href="#django.db">django.db</a>
&#8226; <a href="#django.db.models">django.db.models</a>
&#8226; <a href="#django.db.models.fields">django.db.models.fields</a>
&#8226; <a href="#django.db.models.fields.related">django.db.models.fields.related</a>
&#8226; <a href="#django.db.models.fields.reverse_related">django.db.models.fields.reverse_related</a>
&#8226; <a href="#django.db.utils">django.db.utils</a>
&#8226; <a href="#django.forms">django.forms</a>
&#8226; <a href="#django.forms.boundfield">django.forms.boundfield</a>
&#8226; <a href="#django.forms.fields">django.forms.fields</a>
&#8226; <a href="#django.forms.models">django.forms.models</a>
&#8226; <a href="#django.forms.widgets">django.forms.widgets</a>
&#8226; <a href="#django_extensions">django_extensions</a>
&#8226; <a href="#encodings">encodings</a>
&#8226; <a href="#encodings.aliases">encodings.aliases</a>
@ -306,6 +319,7 @@ imports:
&#8226; <a href="#ntpath">ntpath</a>
&#8226; <a href="#operator">operator</a>
&#8226; <a href="#os">os</a>
&#8226; <a href="#pathlib">pathlib</a>
&#8226; <a href="#posixpath">posixpath</a>
&#8226; <a href="#purchase">purchase</a>
&#8226; <a href="#pyi_rth__tkinter.py">pyi_rth__tkinter.py</a>
@ -326,6 +340,7 @@ imports:
&#8226; <a href="#sre_constants">sre_constants</a>
&#8226; <a href="#sre_parse">sre_parse</a>
&#8226; <a href="#stat">stat</a>
&#8226; <a href="#sys">sys</a>
&#8226; <a href="#traceback">traceback</a>
&#8226; <a href="#types">types</a>
&#8226; <a href="#users">users</a>
@ -801,7 +816,8 @@ imported by:
<a target="code" href="" type="text/plain"><tt>'pygments.lexers'</tt></a>
<span class="moduletype">MissingModule</span> <div class="import">
imported by:
<a href="#django_extensions.templatetags.highlighting">django_extensions.templatetags.highlighting</a>
<a href="#django_extensions.management.debug_cursor">django_extensions.management.debug_cursor</a>
&#8226; <a href="#django_extensions.templatetags.highlighting">django_extensions.templatetags.highlighting</a>
&#8226; <a href="#django_extensions.templatetags.syntax_color">django_extensions.templatetags.syntax_color</a>
</div>
@ -6969,6 +6985,7 @@ imports:
imported by:
<a href="#core">core</a>
&#8226; <a href="#core.apps">core.apps</a>
&#8226; <a href="#core.management">core.management</a>
&#8226; <a href="#core.urls">core.urls</a>
&#8226; <a href="#core.views">core.views</a>
&#8226; <a href="#django">django</a>
@ -6995,6 +7012,83 @@ imported by:
</div>
<div class="node">
<a name="core.management"></a>
<a target="code" href="///D:/Pythoncode/Django_Basic_Manufacturing/core/management/__init__.py" type="text/plain"><tt>core.management</tt></a>
<span class="moduletype">Package</span> <div class="import">
imports:
<a href="#core">core</a>
</div>
<div class="import">
imported by:
<a href="#core.management.commands">core.management.commands</a>
&#8226; <a href="#django">django</a>
</div>
</div>
<div class="node">
<a name="core.management.commands"></a>
<a target="code" href="///D:/Pythoncode/Django_Basic_Manufacturing/core/management/commands/__init__.py" type="text/plain"><tt>core.management.commands</tt></a>
<span class="moduletype">Package</span> <div class="import">
imports:
<a href="#core.management">core.management</a>
</div>
<div class="import">
imported by:
<a href="#core.management.commands.check_database">core.management.commands.check_database</a>
&#8226; <a href="#core.management.commands.populate_test_data">core.management.commands.populate_test_data</a>
&#8226; <a href="#django">django</a>
</div>
</div>
<div class="node">
<a name="core.management.commands.check_database"></a>
<a target="code" href="///D:/Pythoncode/Django_Basic_Manufacturing/core/management/commands/check_database.py" type="text/plain"><tt>core.management.commands.check_database</tt></a>
<span class="moduletype">SourceModule</span> <div class="import">
imports:
<a href="#core.management.commands">core.management.commands</a>
&#8226; <a href="#django.conf">django.conf</a>
&#8226; <a href="#django.core.management.base">django.core.management.base</a>
&#8226; <a href="#os">os</a>
&#8226; <a href="#purchase.models">purchase.models</a>
&#8226; <a href="#sales.models">sales.models</a>
</div>
<div class="import">
imported by:
<a href="#django">django</a>
</div>
</div>
<div class="node">
<a name="core.management.commands.populate_test_data"></a>
<a target="code" href="///D:/Pythoncode/Django_Basic_Manufacturing/core/management/commands/populate_test_data.py" type="text/plain"><tt>core.management.commands.populate_test_data</tt></a>
<span class="moduletype">SourceModule</span> <div class="import">
imports:
<a href="#core.management.commands">core.management.commands</a>
&#8226; <a href="#django.core.management.base">django.core.management.base</a>
&#8226; <a href="#django.db">django.db</a>
&#8226; <a href="#django.db.transaction">django.db.transaction</a>
&#8226; <a href="#purchase.models">purchase.models</a>
&#8226; <a href="#sales.models">sales.models</a>
</div>
<div class="import">
imported by:
<a href="#django">django</a>
</div>
</div>
<div class="node">
<a name="core.urls"></a>
<a target="code" href="///D:/Pythoncode/Django_Basic_Manufacturing/core/urls.py" type="text/plain"><tt>core.urls</tt></a>
@ -7037,10 +7131,14 @@ imports:
&#8226; <a href="#inventory.models">inventory.models</a>
&#8226; <a href="#manufacture.models">manufacture.models</a>
&#8226; <a href="#os">os</a>
&#8226; <a href="#pathlib">pathlib</a>
&#8226; <a href="#purchase.forms">purchase.forms</a>
&#8226; <a href="#purchase.models">purchase.models</a>
&#8226; <a href="#sales.forms">sales.forms</a>
&#8226; <a href="#sales.models">sales.models</a>
&#8226; <a href="#shutil">shutil</a>
&#8226; <a href="#sqlite3">sqlite3</a>
&#8226; <a href="#sys">sys</a>
</div>
<div class="import">
@ -9114,6 +9212,10 @@ imported by:
imports:
<a href="#core">core</a>
&#8226; <a href="#core.apps">core.apps</a>
&#8226; <a href="#core.management">core.management</a>
&#8226; <a href="#core.management.commands">core.management.commands</a>
&#8226; <a href="#core.management.commands.check_database">core.management.commands.check_database</a>
&#8226; <a href="#core.management.commands.populate_test_data">core.management.commands.populate_test_data</a>
&#8226; <a href="#core.urls">core.urls</a>
&#8226; <a href="#core.views">core.views</a>
&#8226; <a href="#crispy_bootstrap5">crispy_bootstrap5</a>
@ -10073,6 +10175,7 @@ imports:
&#8226; <a href="#users.admin">users.admin</a>
&#8226; <a href="#users.apps">users.apps</a>
&#8226; <a href="#users.forms">users.forms</a>
&#8226; <a href="#users.management">users.management</a>
&#8226; <a href="#users.migrations">users.migrations</a>
&#8226; <a href="#users.migrations.0001_initial">users.migrations.0001_initial</a>
&#8226; <a href="#users.migrations.0002_remove_customuser_user_type_remove_usergroup_users_and_more">users.migrations.0002_remove_customuser_user_type_remove_usergroup_users_and_more</a>
@ -10151,6 +10254,7 @@ imported by:
&#8226; <a href="#manufacture.forms">manufacture.forms</a>
&#8226; <a href="#purchase.forms">purchase.forms</a>
&#8226; <a href="#sales.forms">sales.forms</a>
&#8226; <a href="#start_server.py">start_server.py</a>
&#8226; <a href="#users.forms">users.forms</a>
</div>
@ -10373,7 +10477,8 @@ imports:
</div>
<div class="import">
imported by:
<a href="#core.views">core.views</a>
<a href="#core.management.commands.check_database">core.management.commands.check_database</a>
&#8226; <a href="#core.views">core.views</a>
&#8226; <a href="#crispy_forms.templatetags.crispy_forms_field">crispy_forms.templatetags.crispy_forms_field</a>
&#8226; <a href="#crispy_forms.templatetags.crispy_forms_filters">crispy_forms.templatetags.crispy_forms_filters</a>
&#8226; <a href="#crispy_forms.templatetags.crispy_forms_tags">crispy_forms.templatetags.crispy_forms_tags</a>
@ -10590,6 +10695,7 @@ imported by:
&#8226; <a href="#manufacture_app.urls">manufacture_app.urls</a>
&#8226; <a href="#purchase.migrations.0001_initial">purchase.migrations.0001_initial</a>
&#8226; <a href="#sales.migrations.0001_initial">sales.migrations.0001_initial</a>
&#8226; <a href="#start_server.py">start_server.py</a>
&#8226; <a href="#users.migrations.0001_initial">users.migrations.0001_initial</a>
</div>
@ -23134,7 +23240,9 @@ imports:
</div>
<div class="import">
imported by:
<a href="#django">django</a>
<a href="#core.management.commands.check_database">core.management.commands.check_database</a>
&#8226; <a href="#core.management.commands.populate_test_data">core.management.commands.populate_test_data</a>
&#8226; <a href="#django">django</a>
&#8226; <a href="#django.contrib.auth.management.commands.changepassword">django.contrib.auth.management.commands.changepassword</a>
&#8226; <a href="#django.contrib.auth.management.commands.createsuperuser">django.contrib.auth.management.commands.createsuperuser</a>
&#8226; <a href="#django.contrib.gis.management.commands.ogrinspect">django.contrib.gis.management.commands.ogrinspect</a>
@ -24443,7 +24551,8 @@ imports:
</div>
<div class="import">
imported by:
<a href="#core.views">core.views</a>
<a href="#core.management.commands.populate_test_data">core.management.commands.populate_test_data</a>
&#8226; <a href="#core.views">core.views</a>
&#8226; <a href="#django">django</a>
&#8226; <a href="#django.contrib.admin.checks">django.contrib.admin.checks</a>
&#8226; <a href="#django.contrib.admin.filters">django.contrib.admin.filters</a>
@ -24625,6 +24734,7 @@ imported by:
&#8226; <a href="#purchase.models">purchase.models</a>
&#8226; <a href="#sales.migrations.0001_initial">sales.migrations.0001_initial</a>
&#8226; <a href="#sales.models">sales.models</a>
&#8226; <a href="#start_server.py">start_server.py</a>
&#8226; <a href="#users.migrations.0001_initial">users.migrations.0001_initial</a>
&#8226; <a href="#users.migrations.0002_remove_customuser_user_type_remove_usergroup_users_and_more">users.migrations.0002_remove_customuser_user_type_remove_usergroup_users_and_more</a>
&#8226; <a href="#users.models">users.models</a>
@ -26835,6 +26945,7 @@ imported by:
&#8226; <a href="#purchase.models">purchase.models</a>
&#8226; <a href="#sales.migrations.0001_initial">sales.migrations.0001_initial</a>
&#8226; <a href="#sales.models">sales.models</a>
&#8226; <a href="#start_server.py">start_server.py</a>
&#8226; <a href="#users.migrations.0001_initial">users.migrations.0001_initial</a>
&#8226; <a href="#users.migrations.0002_remove_customuser_user_type_remove_usergroup_users_and_more">users.migrations.0002_remove_customuser_user_type_remove_usergroup_users_and_more</a>
&#8226; <a href="#users.models">users.models</a>
@ -27325,6 +27436,7 @@ imported by:
&#8226; <a href="#django.db.models.lookups">django.db.models.lookups</a>
&#8226; <a href="#django.db.models.sql.query">django.db.models.sql.query</a>
&#8226; <a href="#django_extensions.management.commands.sqldiff">django_extensions.management.commands.sqldiff</a>
&#8226; <a href="#start_server.py">start_server.py</a>
</div>
@ -27482,6 +27594,7 @@ imported by:
&#8226; <a href="#django.db.models">django.db.models</a>
&#8226; <a href="#django.db.models.base">django.db.models.base</a>
&#8226; <a href="#django_extensions.management.modelviz">django_extensions.management.modelviz</a>
&#8226; <a href="#start_server.py">start_server.py</a>
</div>
@ -27557,6 +27670,7 @@ imported by:
<a href="#django">django</a>
&#8226; <a href="#django.db.models.fields.related">django.db.models.fields.related</a>
&#8226; <a href="#django_extensions.management.modelviz">django_extensions.management.modelviz</a>
&#8226; <a href="#start_server.py">start_server.py</a>
</div>
@ -28242,7 +28356,8 @@ imports:
</div>
<div class="import">
imported by:
<a href="#django">django</a>
<a href="#core.management.commands.populate_test_data">core.management.commands.populate_test_data</a>
&#8226; <a href="#django">django</a>
&#8226; <a href="#django.contrib.admin.options">django.contrib.admin.options</a>
&#8226; <a href="#django.contrib.auth.admin">django.contrib.auth.admin</a>
&#8226; <a href="#django.contrib.auth.migrations.0011_update_proxy_permissions">django.contrib.auth.migrations.0011_update_proxy_permissions</a>
@ -28300,6 +28415,7 @@ imported by:
&#8226; <a href="#django.db.backends.sqlite3.features">django.db.backends.sqlite3.features</a>
&#8226; <a href="#django.db.models.constraints">django.db.models.constraints</a>
&#8226; <a href="#django.test.signals">django.test.signals</a>
&#8226; <a href="#start_server.py">start_server.py</a>
</div>
@ -28407,6 +28523,7 @@ imported by:
&#8226; <a href="#manufacture.forms">manufacture.forms</a>
&#8226; <a href="#purchase.forms">purchase.forms</a>
&#8226; <a href="#sales.forms">sales.forms</a>
&#8226; <a href="#start_server.py">start_server.py</a>
&#8226; <a href="#users.forms">users.forms</a>
</div>
@ -28434,6 +28551,7 @@ imported by:
&#8226; <a href="#django">django</a>
&#8226; <a href="#django.forms">django.forms</a>
&#8226; <a href="#django.forms.fields">django.forms.fields</a>
&#8226; <a href="#start_server.py">start_server.py</a>
</div>
@ -28481,6 +28599,7 @@ imported by:
&#8226; <a href="#django.forms.formsets">django.forms.formsets</a>
&#8226; <a href="#django.forms.models">django.forms.models</a>
&#8226; <a href="#django.test.testcases">django.test.testcases</a>
&#8226; <a href="#start_server.py">start_server.py</a>
</div>
@ -28578,6 +28697,7 @@ imported by:
&#8226; <a href="#django.contrib.contenttypes.forms">django.contrib.contenttypes.forms</a>
&#8226; <a href="#django.forms">django.forms</a>
&#8226; <a href="#django.views.generic.edit">django.views.generic.edit</a>
&#8226; <a href="#start_server.py">start_server.py</a>
</div>
@ -28687,6 +28807,7 @@ imported by:
&#8226; <a href="#django.forms.forms">django.forms.forms</a>
&#8226; <a href="#django.forms.formsets">django.forms.formsets</a>
&#8226; <a href="#django.forms.models">django.forms.models</a>
&#8226; <a href="#start_server.py">start_server.py</a>
</div>
@ -34745,6 +34866,7 @@ imported by:
<span class="moduletype">SourceModule</span> <div class="import">
imports:
<a href="#'pygments.formatters'">'pygments.formatters'</a>
&#8226; <a href="#'pygments.lexers'">'pygments.lexers'</a>
&#8226; <a href="#contextlib">contextlib</a>
&#8226; <a href="#django.conf">django.conf</a>
&#8226; <a href="#django.core.exceptions">django.core.exceptions</a>
@ -34755,7 +34877,6 @@ imports:
&#8226; <a href="#django.db.backends.utils">django.db.backends.utils</a>
&#8226; <a href="#django_extensions.management">django_extensions.management</a>
&#8226; <a href="#django_extensions.settings">django_extensions.settings</a>
&#8226; <a href="#pygments">pygments</a>
&#8226; <a href="#sqlparse">sqlparse</a>
&#8226; <a href="#time">time</a>
&#8226; <a href="#traceback">traceback</a>
@ -41522,7 +41643,6 @@ imports:
&#8226; <a href="#importlib">importlib</a>
&#8226; <a href="#importlib._bootstrap">importlib._bootstrap</a>
&#8226; <a href="#importlib._bootstrap_external">importlib._bootstrap_external</a>
&#8226; <a href="#importlib.machinery">importlib.machinery</a>
&#8226; <a href="#sys">sys</a>
&#8226; <a href="#warnings">warnings</a>
@ -41719,7 +41839,6 @@ imported by:
<a href="#ctypes.util">ctypes.util</a>
&#8226; <a href="#idlelib.pathbrowser">idlelib.pathbrowser</a>
&#8226; <a href="#imp">imp</a>
&#8226; <a href="#importlib">importlib</a>
&#8226; <a href="#importlib.abc">importlib.abc</a>
&#8226; <a href="#inspect">inspect</a>
&#8226; <a href="#packaging.tags">packaging.tags</a>
@ -42417,6 +42536,8 @@ imported by:
&#8226; <a href="#inventory.views">inventory.views</a>
&#8226; <a href="#manufacture.forms">manufacture.forms</a>
&#8226; <a href="#purchase.forms">purchase.forms</a>
&#8226; <a href="#purchase.models">purchase.models</a>
&#8226; <a href="#sales.admin">sales.admin</a>
&#8226; <a href="#sales.forms">sales.forms</a>
&#8226; <a href="#sales.models">sales.models</a>
@ -43589,6 +43710,7 @@ imports:
&#8226; <a href="#manufacture_app">manufacture_app</a>
&#8226; <a href="#os">os</a>
&#8226; <a href="#pathlib">pathlib</a>
&#8226; <a href="#sys">sys</a>
</div>
<div class="import">
@ -44903,6 +45025,7 @@ imported by:
&#8226; <a href="#concurrent.futures.thread">concurrent.futures.thread</a>
&#8226; <a href="#configparser">configparser</a>
&#8226; <a href="#contextlib">contextlib</a>
&#8226; <a href="#core.management.commands.check_database">core.management.commands.check_database</a>
&#8226; <a href="#core.views">core.views</a>
&#8226; <a href="#ctypes">ctypes</a>
&#8226; <a href="#ctypes._aix">ctypes._aix</a>
@ -45782,6 +45905,7 @@ imported by:
<a href="#PIL.Image">PIL.Image</a>
&#8226; <a href="#PIL._util">PIL._util</a>
&#8226; <a href="#compileall">compileall</a>
&#8226; <a href="#core.views">core.views</a>
&#8226; <a href="#django.conf">django.conf</a>
&#8226; <a href="#django.contrib.admindocs.views">django.contrib.admindocs.views</a>
&#8226; <a href="#django.contrib.auth.password_validation">django.contrib.auth.password_validation</a>
@ -45856,6 +45980,7 @@ imported by:
&#8226; <a href="#setuptools.config.expand">setuptools.config.expand</a>
&#8226; <a href="#setuptools.discovery">setuptools.discovery</a>
&#8226; <a href="#setuptools.dist">setuptools.dist</a>
&#8226; <a href="#start_server.py">start_server.py</a>
&#8226; <a href="#zipfile">zipfile</a>
</div>
@ -55959,7 +56084,8 @@ imports:
</div>
<div class="import">
imported by:
<a href="#django">django</a>
<a href="#core.views">core.views</a>
&#8226; <a href="#django">django</a>
&#8226; <a href="#purchase.views">purchase.views</a>
</div>
@ -56018,12 +56144,15 @@ imports:
&#8226; <a href="#django.utils">django.utils</a>
&#8226; <a href="#django.utils.timezone">django.utils.timezone</a>
&#8226; <a href="#django.utils.translation">django.utils.translation</a>
&#8226; <a href="#inventory.models">inventory.models</a>
&#8226; <a href="#purchase">purchase</a>
</div>
<div class="import">
imported by:
<a href="#core.views">core.views</a>
<a href="#core.management.commands.check_database">core.management.commands.check_database</a>
&#8226; <a href="#core.management.commands.populate_test_data">core.management.commands.populate_test_data</a>
&#8226; <a href="#core.views">core.views</a>
&#8226; <a href="#django">django</a>
&#8226; <a href="#inventory.views">inventory.views</a>
&#8226; <a href="#purchase.admin">purchase.admin</a>
@ -56267,8 +56396,7 @@ imported by:
<a target="code" href="" type="text/plain"><tt>pygments</tt></a>
<span class="moduletype">MissingModule</span> <div class="import">
imported by:
<a href="#django_extensions.management.debug_cursor">django_extensions.management.debug_cursor</a>
&#8226; <a href="#django_extensions.templatetags.highlighting">django_extensions.templatetags.highlighting</a>
<a href="#django_extensions.templatetags.highlighting">django_extensions.templatetags.highlighting</a>
&#8226; <a href="#django_extensions.templatetags.syntax_color">django_extensions.templatetags.syntax_color</a>
</div>
@ -57053,6 +57181,7 @@ imported by:
imports:
<a href="#django.contrib">django.contrib</a>
&#8226; <a href="#django.contrib.admin">django.contrib.admin</a>
&#8226; <a href="#inventory.models">inventory.models</a>
&#8226; <a href="#sales">sales</a>
&#8226; <a href="#sales.models">sales.models</a>
@ -57096,7 +57225,8 @@ imports:
</div>
<div class="import">
imported by:
<a href="#django">django</a>
<a href="#core.views">core.views</a>
&#8226; <a href="#django">django</a>
&#8226; <a href="#sales.views">sales.views</a>
</div>
@ -57161,7 +57291,9 @@ imports:
</div>
<div class="import">
imported by:
<a href="#core.views">core.views</a>
<a href="#core.management.commands.check_database">core.management.commands.check_database</a>
&#8226; <a href="#core.management.commands.populate_test_data">core.management.commands.populate_test_data</a>
&#8226; <a href="#core.views">core.views</a>
&#8226; <a href="#django">django</a>
&#8226; <a href="#inventory.views">inventory.views</a>
&#8226; <a href="#sales.admin">sales.admin</a>
@ -62655,6 +62787,7 @@ imported by:
&#8226; <a href="#concurrent.futures.process">concurrent.futures.process</a>
&#8226; <a href="#configparser">configparser</a>
&#8226; <a href="#contextlib">contextlib</a>
&#8226; <a href="#core.views">core.views</a>
&#8226; <a href="#crispy_forms.utils">crispy_forms.utils</a>
&#8226; <a href="#ctypes">ctypes</a>
&#8226; <a href="#ctypes._aix">ctypes._aix</a>
@ -62798,6 +62931,7 @@ imported by:
&#8226; <a href="#linecache">linecache</a>
&#8226; <a href="#locale">locale</a>
&#8226; <a href="#logging">logging</a>
&#8226; <a href="#manufacture_app.settings">manufacture_app.settings</a>
&#8226; <a href="#mimetypes">mimetypes</a>
&#8226; <a href="#multiprocessing">multiprocessing</a>
&#8226; <a href="#multiprocessing.connection">multiprocessing.connection</a>
@ -63005,6 +63139,7 @@ imported by:
&#8226; <a href="#socketserver">socketserver</a>
&#8226; <a href="#sqlparse.cli">sqlparse.cli</a>
&#8226; <a href="#ssl">ssl</a>
&#8226; <a href="#start_server.py">start_server.py</a>
&#8226; <a href="#statistics">statistics</a>
&#8226; <a href="#subprocess">subprocess</a>
&#8226; <a href="#sysconfig">sysconfig</a>
@ -65851,6 +65986,7 @@ imported by:
&#8226; <a href="#users.admin">users.admin</a>
&#8226; <a href="#users.apps">users.apps</a>
&#8226; <a href="#users.forms">users.forms</a>
&#8226; <a href="#users.management">users.management</a>
&#8226; <a href="#users.migrations">users.migrations</a>
&#8226; <a href="#users.models">users.models</a>
&#8226; <a href="#users.urls">users.urls</a>
@ -65920,6 +66056,22 @@ imported by:
</div>
<div class="node">
<a name="users.management"></a>
<a target="code" href="///D:/Pythoncode/Django_Basic_Manufacturing/users/management/__init__.py" type="text/plain"><tt>users.management</tt></a>
<span class="moduletype">Package</span> <div class="import">
imports:
<a href="#users">users</a>
</div>
<div class="import">
imported by:
<a href="#django">django</a>
</div>
</div>
<div class="node">
<a name="users.migrations"></a>
<a target="code" href="///D:/Pythoncode/Django_Basic_Manufacturing/users/migrations/__init__.py" type="text/plain"><tt>users.migrations</tt></a>

View File

View File

View File

@ -0,0 +1,43 @@
from django.core.management.base import BaseCommand
from django.conf import settings
import os
class Command(BaseCommand):
help = 'Check database path and contents'
def handle(self, *args, **options):
# Show database path
db_path = settings.DATABASES['default']['NAME']
self.stdout.write(f'Database path: {db_path}')
self.stdout.write(f'Database exists: {os.path.exists(db_path)}')
if os.path.exists(db_path):
# Check file size
file_size = os.path.getsize(db_path)
self.stdout.write(f'Database size: {file_size} bytes ({file_size / 1024:.2f} KB)')
# Try to access some data
try:
from purchase.models import Supplier
from sales.models import Customer
supplier_count = Supplier.objects.count()
customer_count = Customer.objects.count()
self.stdout.write(f'Suppliers in database: {supplier_count}')
self.stdout.write(f'Customers in database: {customer_count}')
if supplier_count > 0:
self.stdout.write('First 3 suppliers:')
for supplier in Supplier.objects.all()[:3]:
self.stdout.write(f' - {supplier.name} ({supplier.code})')
if customer_count > 0:
self.stdout.write('First 3 customers:')
for customer in Customer.objects.all()[:3]:
self.stdout.write(f' - {customer.name} ({customer.code})')
except Exception as e:
self.stdout.write(f'Error accessing database contents: {e}')
else:
self.stdout.write('Database file not found')

View File

@ -0,0 +1,48 @@
from django.core.management.base import BaseCommand
from django.db import transaction
from purchase.models import Supplier
from sales.models import Customer
class Command(BaseCommand):
help = 'Populate test suppliers and customers'
def handle(self, *args, **options):
# Create test suppliers
suppliers_data = [
{'name': 'ABC Supplier', 'code': 'SUP001', 'contact_person': 'John Doe', 'email': 'john@abcsupplier.com'},
{'name': 'XYZ Supplier', 'code': 'SUP002', 'contact_person': 'Jane Smith', 'email': 'jane@xyzsupplier.com'},
{'name': 'Global Parts', 'code': 'SUP003', 'contact_person': 'Bob Johnson', 'email': 'bob@globalparts.com'},
]
created_suppliers = 0
for supplier_data in suppliers_data:
supplier, created = Supplier.objects.get_or_create(
code=supplier_data['code'],
defaults=supplier_data
)
if created:
created_suppliers += 1
self.stdout.write(f'Created supplier: {supplier.name}')
# Create test customers
customers_data = [
{'name': 'Tech Solutions Inc.', 'code': 'CUST01', 'contact_person': 'Alice Brown', 'email': 'alice@techsolutions.com'},
{'name': 'Global Enterprises', 'code': 'CUST002', 'contact_person': 'Charlie Wilson', 'email': 'charlie@globalenterprises.com'},
{'name': 'Local Business', 'code': 'CUST03', 'contact_person': 'Diana Prince', 'email': 'diana@localbusiness.com'},
]
created_customers = 0
for customer_data in customers_data:
customer, created = Customer.objects.get_or_create(
code=customer_data['code'],
defaults=customer_data
)
if created:
created_customers += 1
self.stdout.write(f'Created customer: {customer.name}')
self.stdout.write(
self.style.SUCCESS(
f'Successfully populated test data: {created_suppliers} suppliers, {created_customers} customers'
)
)

View File

@ -10,4 +10,7 @@ urlpatterns = [
path('database/', login_required(views.DatabaseManagementView.as_view()), name='database_management'),
path('database/backup/', login_required(views.backup_database), name='backup_database'),
path('database/restore/', login_required(views.restore_database), name='restore_database'),
path('database/change-password/', login_required(views.change_password), name='change_password'),
path('test-form/', login_required(views.test_form), name='test_form'),
path('simple-test/', login_required(views.simple_test), name='simple_test'),
]

View File

@ -8,10 +8,12 @@ from django.conf import settings
import sqlite3
import os
import shutil
import sys
from datetime import datetime, timedelta
from django.utils import timezone
from django.db.models import Sum, Count, Avg
from decimal import Decimal
from pathlib import Path
from inventory.models import Product, StockMovement
from purchase.models import PurchaseOrder, PurchaseOrderItem
@ -152,19 +154,33 @@ class DatabaseManagementView(TemplateView):
context = super().get_context_data(**kwargs)
# Database file information
db_path = os.path.join(settings.BASE_DIR, 'db.sqlite3')
# Use the same path logic as in settings.py
if getattr(sys, 'frozen', False):
# Running as compiled executable
base_db_dir = Path(os.path.dirname(sys.executable))
else:
# Running as script
base_db_dir = settings.BASE_DIR
db_path = base_db_dir / 'db.sqlite3'
if os.path.exists(db_path):
stat = os.stat(db_path)
context['db_size'] = f"{stat.st_size / (1024*1024):.2f} MB"
context['db_modified'] = datetime.fromtimestamp(stat.st_mtime).strftime('%Y-%m-%d %H:%M:%S')
# Available backups
backup_dir = os.path.join(settings.BASE_DIR, 'backups')
# Use the same path logic as in settings.py
if getattr(sys, 'frozen', False):
# Running as compiled executable
base_db_dir = Path(os.path.dirname(sys.executable))
else:
# Running as script
base_db_dir = settings.BASE_DIR
backup_dir = base_db_dir / 'backups'
if os.path.exists(backup_dir):
backups = []
for file in os.listdir(backup_dir):
if file.endswith('.sqlite3'):
file_path = os.path.join(backup_dir, file)
file_path = backup_dir / file
stat = os.stat(file_path)
backups.append({
'name': file,
@ -200,17 +216,25 @@ class DatabaseManagementView(TemplateView):
def backup_database(request):
if request.method == 'POST':
try:
# Use the same path logic as in settings.py
if getattr(sys, 'frozen', False):
# Running as compiled executable
base_db_dir = Path(os.path.dirname(sys.executable))
else:
# Running as script
base_db_dir = settings.BASE_DIR
# Create backup directory if it doesn't exist
backup_dir = os.path.join(settings.BASE_DIR, 'backups')
backup_dir = base_db_dir / 'backups'
os.makedirs(backup_dir, exist_ok=True)
# Generate backup filename with timestamp
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
backup_filename = f'manufacture_app_backup_{timestamp}.sqlite3'
backup_path = os.path.join(backup_dir, backup_filename)
backup_path = backup_dir / backup_filename
# Copy the database file
db_path = os.path.join(settings.BASE_DIR, 'db.sqlite3')
db_path = base_db_dir / 'db.sqlite3'
shutil.copy2(db_path, backup_path)
messages.success(request, f'Database backed up successfully to {backup_filename}')
@ -219,17 +243,147 @@ def backup_database(request):
return redirect('core:database_management')
def test_form(request):
"""Test view to isolate form rendering issue"""
# Check database connectivity
db_connected = False
db_error = None
try:
from django.db import connection
connection.ensure_connection()
db_connected = True
except Exception as e:
db_error = str(e)
from purchase.forms import PurchaseOrderForm
from sales.forms import SaleOrderForm
purchase_form = PurchaseOrderForm()
sales_form = SaleOrderForm()
# Check if suppliers/customers are available
suppliers = []
customers = []
supplier_count = 0
customer_count = 0
model_error = None
try:
from purchase.models import Supplier
from sales.models import Customer
suppliers = Supplier.objects.all()
customers = Customer.objects.all()
supplier_count = suppliers.count()
customer_count = customers.count()
except Exception as e:
model_error = str(e)
# Debug information
purchase_form_debug = {
'field_type': type(purchase_form.fields['supplier']).__name__,
'choices_count': len(purchase_form.fields['supplier'].choices) if hasattr(purchase_form.fields['supplier'], 'choices') else 'N/A',
'choices': list(purchase_form.fields['supplier'].choices) if hasattr(purchase_form.fields['supplier'], 'choices') else [],
}
sales_form_debug = {
'field_type': type(sales_form.fields['customer']).__name__,
'choices_count': len(sales_form.fields['customer'].choices) if hasattr(sales_form.fields['customer'], 'choices') else 'N/A',
'choices': list(sales_form.fields['customer'].choices) if hasattr(sales_form.fields['customer'], 'choices') else [],
}
context = {
'purchase_form': purchase_form,
'sales_form': sales_form,
'suppliers': suppliers,
'customers': customers,
'supplier_count': supplier_count,
'customer_count': customer_count,
'db_connected': db_connected,
'db_error': db_error,
'model_error': model_error,
'purchase_form_debug': purchase_form_debug,
'sales_form_debug': sales_form_debug,
}
return render(request, 'core/test_form.html', context)
@login_required
@user_passes_test(is_admin)
def change_password(request):
if request.method == 'POST':
try:
username = request.POST.get('username')
new_password = request.POST.get('new_password')
confirm_password = request.POST.get('confirm_password')
if not username:
messages.error(request, 'Username is required.')
return redirect('core:database_management')
if not new_password:
messages.error(request, 'New password is required.')
return redirect('core:database_management')
if new_password != confirm_password:
messages.error(request, 'Passwords do not match.')
return redirect('core:database_management')
# Validate password length
if len(new_password) < 8:
messages.error(request, 'Password must be at least 8 characters long.')
return redirect('core:database_management')
# Get user and change password
from django.contrib.auth import get_user_model
User = get_user_model()
try:
user = User.objects.get(username=username)
user.set_password(new_password)
user.save()
messages.success(request, f'Successfully changed password for user "{username}"')
except User.DoesNotExist:
messages.error(request, f'User "{username}" does not exist.')
except Exception as e:
messages.error(request, f'Failed to change password: {str(e)}')
return redirect('core:database_management')
@login_required
def simple_test(request):
"""Simple test view to isolate form rendering issue"""
from purchase.forms import PurchaseOrderForm
from sales.forms import SaleOrderForm
purchase_form = PurchaseOrderForm()
sales_form = SaleOrderForm()
context = {
'purchase_form': purchase_form,
'sales_form': sales_form,
}
return render(request, 'core/simple_test.html', context)
@login_required
@user_passes_test(is_admin)
def restore_database(request):
if request.method == 'POST':
try:
# Use the same path logic as in settings.py
if getattr(sys, 'frozen', False):
# Running as compiled executable
base_db_dir = Path(os.path.dirname(sys.executable))
else:
# Running as script
base_db_dir = settings.BASE_DIR
backup_file = request.FILES.get('backup_file')
if backup_file:
# Create backup of current database first
current_db = os.path.join(settings.BASE_DIR, 'db.sqlite3')
current_db = base_db_dir / 'db.sqlite3'
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
safety_backup = os.path.join(settings.BASE_DIR, f'safety_backup_{timestamp}.sqlite3')
safety_backup = base_db_dir / f'safety_backup_{timestamp}.sqlite3'
shutil.copy2(current_db, safety_backup)
# Restore from uploaded backup

Binary file not shown.

BIN
dist/start_server.exe vendored

Binary file not shown.

View File

@ -27,7 +27,7 @@ class SupplierForm(forms.ModelForm):
class Meta:
model = Supplier
fields = ['code', 'name', 'contact_person', 'email', 'phone', 'address', 'rating', 'credit_limit', 'is_active']
fields = ['code', 'name', 'contact_person', 'email', 'phone', 'address', 'credit_limit', 'is_active']
widgets = {
'address': forms.Textarea(attrs={'rows': 3}),
}

View File

@ -0,0 +1,17 @@
# Generated by Django 5.2.5 on 2025-08-19 08:54
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('inventory', '0002_customer_supplier'),
]
operations = [
migrations.RemoveField(
model_name='supplier',
name='rating',
),
]

View File

@ -234,10 +234,6 @@ class Supplier(models.Model):
email = models.EmailField(blank=True)
phone = models.CharField(max_length=20, blank=True)
address = models.TextField(blank=True)
rating = models.PositiveSmallIntegerField(
validators=[MinValueValidator(1), MaxValueValidator(5)],
default=3
)
credit_limit = models.DecimalField(max_digits=12, decimal_places=2, default=0)
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)

View File

@ -1,5 +1,5 @@
from django.contrib import admin
from .models import ManufacturingOrder, ManufacturingLine, ManufacturingOrderLine
from .models import ManufacturingOrder, ManufacturingLine, ManufacturingOrderLine, BillOfMaterials, BillOfMaterialsTotal
@admin.register(ManufacturingOrder)
class ManufacturingOrderAdmin(admin.ModelAdmin):
@ -20,6 +20,7 @@ class ManufacturingOrderAdmin(admin.ModelAdmin):
def get_queryset(self, request):
return super().get_queryset(request).select_related('product', 'created_by')
@admin.register(ManufacturingLine)
class ManufacturingLineAdmin(admin.ModelAdmin):
list_display = ('name', 'capacity_per_hour', 'is_active', 'created_at')
@ -32,6 +33,7 @@ class ManufacturingLineAdmin(admin.ModelAdmin):
('Status', {'fields': ('is_active',)}),
)
@admin.register(ManufacturingOrderLine)
class ManufacturingOrderLineAdmin(admin.ModelAdmin):
list_display = ('manufacturing_order', 'manufacturing_line', 'actual_quantity',
@ -48,3 +50,35 @@ class ManufacturingOrderLineAdmin(admin.ModelAdmin):
def get_queryset(self, request):
return super().get_queryset(request).select_related('manufacturing_order', 'manufacturing_line')
@admin.register(BillOfMaterials)
class BillOfMaterialsAdmin(admin.ModelAdmin):
list_display = ('manufactured_product', 'component', 'quantity', 'unit', 'created_at')
list_filter = ('manufactured_product__category', 'created_at')
search_fields = ('manufactured_product__name', 'manufactured_product__code',
'component__name', 'component__code')
ordering = ('manufactured_product__name', 'component__name')
fieldsets = (
('BOM Information', {'fields': ('manufactured_product', 'component', 'quantity', 'unit')}),
)
def get_queryset(self, request):
return super().get_queryset(request).select_related('manufactured_product', 'component')
@admin.register(BillOfMaterialsTotal)
class BillOfMaterialsTotalAdmin(admin.ModelAdmin):
list_display = ('bom', 'total_cost', 'total_weight', 'last_calculated')
list_filter = ('last_calculated',)
search_fields = ('bom__manufactured_product__name', 'bom__component__name')
ordering = ('-last_calculated',)
fieldsets = (
('BOM Total Information', {'fields': ('bom', 'total_cost', 'total_weight')}),
)
readonly_fields = ('last_calculated',)
def get_queryset(self, request):
return super().get_queryset(request).select_related('bom__manufactured_product', 'bom__component')

View File

@ -1,5 +1,7 @@
from django import forms
from .models import ManufacturingOrder
from django.forms import formset_factory
from django.forms.widgets import NumberInput
from .models import ManufacturingOrder, BillOfMaterials
from inventory.models import Product
class ManufacturingOrderForm(forms.ModelForm):
@ -10,6 +12,7 @@ class ManufacturingOrderForm(forms.ModelForm):
fields = ['product', 'quantity', 'date', 'notes']
widgets = {
'date': forms.DateInput(attrs={'type': 'date'}),
'quantity': forms.NumberInput(attrs={'step': '0.01'}),
'notes': forms.Textarea(attrs={'rows': 3}),
}
@ -31,4 +34,145 @@ class ManufacturingOrderForm(forms.ModelForm):
quantity = self.cleaned_data.get('quantity')
if quantity <= 0:
raise forms.ValidationError("Quantity must be greater than zero.")
return quantity
return quantity
class BillOfMaterialsForm(forms.ModelForm):
"""Form for creating and editing Bill of Materials entries"""
class Meta:
model = BillOfMaterials
fields = ['manufactured_product', 'component', 'quantity', 'unit']
widgets = {
'quantity': forms.NumberInput(attrs={'step': '0.0001'}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Add Bootstrap classes
for field in self.fields.values():
if isinstance(field.widget, (forms.CheckboxInput, forms.RadioSelect)):
field.widget.attrs.update({'class': 'form-check-input'})
elif isinstance(field.widget, forms.Textarea):
field.widget.attrs.update({'class': 'form-control'})
elif isinstance(field.widget, forms.DateInput):
field.widget.attrs.update({'class': 'form-control'})
else:
field.widget.attrs.update({'class': 'form-control'})
# Filter manufactured products to only show those marked as manufactured
self.fields['manufactured_product'].queryset = Product.objects.filter(is_manufactured=True)
def clean_quantity(self):
"""Ensure quantity is positive"""
quantity = self.cleaned_data.get('quantity')
if quantity <= 0:
raise forms.ValidationError("Quantity must be greater than zero.")
return quantity
def clean(self):
"""Validate that manufactured product and component are not the same"""
cleaned_data = super().clean()
manufactured_product = cleaned_data.get('manufactured_product')
component = cleaned_data.get('component')
if manufactured_product and component and manufactured_product == component:
raise forms.ValidationError("A product cannot be a component of itself.")
return cleaned_data
class MultipleBillOfMaterialsForm(forms.Form):
"""Form for creating multiple BOM entries at once"""
manufactured_product = forms.ModelChoiceField(
queryset=Product.objects.filter(is_manufactured=True),
label="Manufactured Product",
help_text="The product that is manufactured using these components"
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Add Bootstrap classes
for field in self.fields.values():
if isinstance(field.widget, (forms.CheckboxInput, forms.RadioSelect)):
field.widget.attrs.update({'class': 'form-check-input'})
elif isinstance(field.widget, forms.Textarea):
field.widget.attrs.update({'class': 'form-control'})
elif isinstance(field.widget, forms.DateInput):
field.widget.attrs.update({'class': 'form-control'})
else:
field.widget.attrs.update({'class': 'form-control'})
def clean_manufactured_product(self):
"""Ensure the selected product is marked as manufactured"""
manufactured_product = self.cleaned_data.get('manufactured_product')
if manufactured_product and not manufactured_product.is_manufactured:
raise forms.ValidationError("Selected product is not marked as manufactured.")
return manufactured_product
class BOMComponentForm(forms.Form):
"""Form for individual BOM component entry"""
component = forms.ModelChoiceField(
queryset=Product.objects.all(),
label="Component",
help_text="A component used in manufacturing"
)
quantity = forms.DecimalField(
max_digits=10,
decimal_places=4,
min_value=0.0001,
label="Quantity",
help_text="Quantity of component needed per unit of manufactured product"
)
unit = forms.CharField(
max_length=20,
required=False,
label="Unit",
help_text="Unit of measurement for the component (e.g., kg, pieces, meters)",
widget=forms.TextInput(attrs={'readonly': 'readonly'})
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Add Bootstrap classes
for field in self.fields.values():
if isinstance(field.widget, (forms.CheckboxInput, forms.RadioSelect)):
field.widget.attrs.update({'class': 'form-check-input'})
elif isinstance(field.widget, forms.Textarea):
field.widget.attrs.update({'class': 'form-control'})
elif isinstance(field.widget, forms.DateInput):
field.widget.attrs.update({'class': 'form-control'})
else:
field.widget.attrs.update({'class': 'form-control'})
def clean_quantity(self):
"""Ensure quantity is positive"""
quantity = self.cleaned_data.get('quantity')
if quantity <= 0:
raise forms.ValidationError("Quantity must be greater than zero.")
return quantity
def clean(self):
"""Validate that component is provided"""
cleaned_data = super().clean()
component = cleaned_data.get('component')
# If no component is selected, this might be an empty form
if not component:
# Check if any other fields have data
quantity = cleaned_data.get('quantity')
unit = cleaned_data.get('unit')
# If other fields have data but no component, raise error
if quantity or unit:
raise forms.ValidationError("Component is required.")
# If all fields are empty, mark form as empty
raise forms.ValidationError("This form is empty.")
return cleaned_data
# Create a formset for BOM components
BOMComponentFormSet = formset_factory(BOMComponentForm, extra=1, min_num=1, validate_min=True)

View File

@ -0,0 +1,49 @@
# Generated by Django 5.2.5 on 2025-08-19 08:54
import django.core.validators
import django.db.models.deletion
from decimal import Decimal
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0003_remove_supplier_rating'),
('manufacture', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='BillOfMaterials',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('quantity', models.DecimalField(decimal_places=4, help_text='Quantity of component needed per unit of manufactured product', max_digits=10, validators=[django.core.validators.MinValueValidator(Decimal('0.0001'))])),
('unit', models.CharField(blank=True, help_text='Unit of measurement for the component (e.g., kg, pieces, meters)', max_length=20)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('component', models.ForeignKey(help_text='A component used in manufacturing', on_delete=django.db.models.deletion.CASCADE, related_name='bom_components', to='inventory.product')),
('manufactured_product', models.ForeignKey(help_text='The product that is manufactured using this BOM', on_delete=django.db.models.deletion.CASCADE, related_name='bom_manufactured', to='inventory.product')),
],
options={
'verbose_name': 'Bill of Materials',
'verbose_name_plural': 'Bills of Materials',
'ordering': ['manufactured_product__name', 'component__name'],
'unique_together': {('manufactured_product', 'component')},
},
),
migrations.CreateModel(
name='BillOfMaterialsTotal',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('total_cost', models.DecimalField(decimal_places=4, default=0, max_digits=12)),
('total_weight', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)),
('last_calculated', models.DateTimeField(auto_now=True)),
('bom', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='totals', to='manufacture.billofmaterials')),
],
options={
'verbose_name': 'BOM Total',
'verbose_name_plural': 'BOM Totals',
},
),
]

View File

@ -60,11 +60,51 @@ class ManufacturingOrder(models.Model):
def save(self, *args, **kwargs):
"""Override save to calculate total cost and update product stock"""
# Calculate total cost
self.total_cost = self.labor_cost + self.overhead_cost
# Check if product has BOM entries
bom_entries = BillOfMaterials.objects.filter(manufactured_product=self.product)
# Calculate total cost based on BOM if available, otherwise use manual costs
if bom_entries.exists():
# Calculate total cost based on BOM
bom_total_cost = 0
for bom_entry in bom_entries:
component_cost = bom_entry.get_total_component_cost()
bom_total_cost += component_cost * self.quantity
# Add labor and overhead costs
self.total_cost = bom_total_cost + self.labor_cost + self.overhead_cost
else:
# Calculate total cost using manual costs
self.total_cost = self.labor_cost + self.overhead_cost
# If this is a new order and status is completed, add to inventory
if not self.pk and self.status == 'completed':
# Check if product has BOM entries and deduct components from stock
if bom_entries.exists():
# Check if there's enough stock for all components
insufficient_stock = []
for bom_entry in bom_entries:
required_quantity = bom_entry.quantity * self.quantity
if bom_entry.component.current_stock < required_quantity:
insufficient_stock.append({
'component': bom_entry.component.name,
'required': required_quantity,
'available': bom_entry.component.current_stock
})
# If there's insufficient stock, raise an error
if insufficient_stock:
error_msg = "Insufficient stock for components: "
for item in insufficient_stock:
error_msg += f"{item['component']} (required: {item['required']}, available: {item['available']}) "
raise ValueError(error_msg)
# Deduct components from stock
for bom_entry in bom_entries:
required_quantity = bom_entry.quantity * self.quantity
bom_entry.component.current_stock -= required_quantity
bom_entry.component.save()
# Update product stock
self.product.current_stock += self.quantity
@ -101,6 +141,7 @@ class ManufacturingOrder(models.Model):
return ((self.product.selling_price - unit_cost) / unit_cost) * 100
return Decimal('0')
class ManufacturingLine(models.Model):
"""Manufacturing line/workstation information"""
@ -125,6 +166,7 @@ class ManufacturingLine(models.Model):
def __str__(self):
return self.name
class ManufacturingOrderLine(models.Model):
"""Individual line items for manufacturing orders (optional for future expansion)"""
@ -151,3 +193,77 @@ class ManufacturingOrderLine(models.Model):
if self.start_time and self.end_time:
return self.end_time - self.start_time
return None
class BillOfMaterials(models.Model):
"""Bill of Materials - defines components needed to manufacture a product"""
manufactured_product = models.ForeignKey(
'inventory.Product',
on_delete=models.CASCADE,
related_name='bom_manufactured',
help_text='The product that is manufactured using this BOM'
)
component = models.ForeignKey(
'inventory.Product',
on_delete=models.CASCADE,
related_name='bom_components',
help_text='A component used in manufacturing'
)
quantity = models.DecimalField(
max_digits=10,
decimal_places=4,
validators=[MinValueValidator(Decimal('0.0001'))],
help_text='Quantity of component needed per unit of manufactured product'
)
unit = models.CharField(
max_length=20,
blank=True,
help_text='Unit of measurement for the component (e.g., kg, pieces, meters)'
)
# Timestamps
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
unique_together = ('manufactured_product', 'component')
ordering = ['manufactured_product__name', 'component__name']
verbose_name = _('Bill of Materials')
verbose_name_plural = _('Bills of Materials')
def __str__(self):
return f"{self.manufactured_product.name} - {self.component.name} ({self.quantity} {self.unit})"
def save(self, *args, **kwargs):
"""Override save to ensure the manufactured product is marked as manufactured"""
# Ensure the manufactured product is marked as manufactured
if not self.manufactured_product.is_manufactured:
self.manufactured_product.is_manufactured = True
self.manufactured_product.save()
# If unit is not provided, use the component's unit
if not self.unit and self.component.unit:
self.unit = self.component.unit
super().save(*args, **kwargs)
def get_total_component_cost(self):
"""Calculate total cost of this component for one unit of manufactured product"""
return self.quantity * self.component.cost_price
class BillOfMaterialsTotal(models.Model):
"""Pre-calculated totals for a BOM to improve performance"""
bom = models.OneToOneField(BillOfMaterials, on_delete=models.CASCADE, related_name='totals')
total_cost = models.DecimalField(max_digits=12, decimal_places=4, default=0)
total_weight = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
last_calculated = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = _('BOM Total')
verbose_name_plural = _('BOM Totals')
def __str__(self):
return f"{self.bom} - Total: {self.total_cost}"

View File

View File

@ -0,0 +1,79 @@
from django import template
register = template.Library()
@register.filter
def format_number(value, decimal_places=2):
"""
Format a decimal value to display with Indonesian number formatting.
Uses '.' as thousand separator and ',' as decimal separator.
Removes trailing zeros after decimal.
"""
if value is None:
return ''
try:
# Convert to float
num = float(value)
# Format with specified decimal places
formatted = f"{num:.{decimal_places}f}"
# Split into integer and decimal parts
if '.' in formatted:
integer_part, decimal_part = formatted.split('.')
else:
integer_part, decimal_part = formatted, ''
# Add thousand separators to integer part
# Reverse the string, add separators every 3 digits, then reverse back
integer_part = '{:,}'.format(int(integer_part)).replace(',', '.')
# Handle decimal part - remove trailing zeros
if decimal_part and int(decimal_part) != 0:
# Remove trailing zeros
decimal_part = decimal_part.rstrip('0')
return f"{integer_part},{decimal_part}"
else:
return integer_part
except (ValueError, TypeError):
return str(value)
@register.filter
def format_quantity(value):
"""
Format a decimal value to display with Indonesian number formatting for quantities.
Uses '.' as thousand separator and ',' as decimal separator.
Removes trailing zeros after decimal.
"""
return format_number(value, 4)
@register.filter
def format_currency(value):
"""
Format a decimal value to display with Indonesian number formatting for currency.
Uses '.' as thousand separator and ',' as decimal separator.
Always shows 2 decimal places for currency.
"""
if value is None:
return ''
try:
# Convert to float
num = float(value)
# Format with 2 decimal places for currency
formatted = f"{num:,.2f}"
# Split into integer and decimal parts
integer_part, decimal_part = formatted.split('.')
# Add thousand separators to integer part
integer_part = '{:,}'.format(int(integer_part)).replace(',', '.')
# Combine with decimal part
return f"{integer_part},{decimal_part}"
except (ValueError, TypeError):
return str(value)

View File

@ -8,5 +8,14 @@ urlpatterns = [
path('', views.ManufactureListView.as_view(), name='manufacture_list'),
path('create/', views.manufacture_create, name='manufacture_create'),
path('<int:pk>/', views.ManufactureDetailView.as_view(), name='manufacture_detail'),
path('<int:pk>/edit/', views.manufacture_edit, name='manufacture_edit'),
path('<int:pk>/delete/', views.manufacture_delete, name='manufacture_delete'),
# BOM URLs
path('bom/', views.BillOfMaterialsListView.as_view(), name='bom_list'),
path('bom/create/', views.bom_create, name='bom_create'),
path('bom/<int:pk>/', views.BillOfMaterialsDetailView.as_view(), name='bom_detail'),
path('bom/<int:pk>/edit/', views.bom_edit, name='bom_edit'),
path('bom/<int:pk>/delete/', views.bom_delete, name='bom_delete'),
path('bom/product/<int:product_id>/info/', views.get_product_info, name='get_product_info'),
]

View File

@ -1,10 +1,13 @@
from django.shortcuts import render, redirect
from django.shortcuts import render, redirect, get_object_or_404
from django.views.generic import ListView, DetailView
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.contrib import messages
from .models import ManufacturingOrder
from .forms import ManufacturingOrderForm
from django.http import JsonResponse
from .models import ManufacturingOrder, BillOfMaterials
from inventory.models import Product
from .forms import ManufacturingOrderForm, BillOfMaterialsForm, MultipleBillOfMaterialsForm, BOMComponentFormSet
from users.views import has_manufacturing_access
@method_decorator(login_required, name='dispatch')
class ManufactureListView(ListView):
@ -13,6 +16,23 @@ class ManufactureListView(ListView):
context_object_name = 'manufacturing_orders'
paginate_by = 20
@method_decorator(login_required, name='dispatch')
class BillOfMaterialsListView(ListView):
model = BillOfMaterials
template_name = 'manufacture/bom_list.html'
context_object_name = 'boms'
paginate_by = 20
def get_queryset(self):
queryset = super().get_queryset()
# Optionally filter by manufactured product
product_id = self.request.GET.get('product_id')
if product_id:
queryset = queryset.filter(manufactured_product_id=product_id)
return queryset
@login_required
def manufacture_create(request):
"""Create a new manufacturing order with simple input"""
@ -46,11 +66,36 @@ def manufacture_create(request):
return render(request, 'manufacture/manufacture_form.html', {'form': form, 'title': 'Create Manufacturing Order'})
@login_required
def manufacture_edit(request, pk):
"""Edit an existing manufacturing order"""
if not has_manufacturing_access(request.user):
messages.error(request, 'You do not have permission to edit manufacturing orders.')
return redirect('manufacture:manufacture_detail', pk=pk)
manufacturing_order = get_object_or_404(ManufacturingOrder, pk=pk)
if request.method == 'POST':
form = ManufacturingOrderForm(request.POST, instance=manufacturing_order)
if form.is_valid():
form.save()
messages.success(request, f'Manufacturing order "{manufacturing_order.order_number}" updated successfully!')
return redirect('manufacture:manufacture_detail', pk=manufacturing_order.pk)
else:
form = ManufacturingOrderForm(instance=manufacturing_order)
return render(request, 'manufacture/manufacture_form.html', {
'form': form,
'title': 'Edit Manufacturing Order',
'manufacturing_order': manufacturing_order
})
@login_required
def manufacture_delete(request, pk):
"""Delete a manufacturing order"""
from users.views import is_administrator
if not is_administrator(request.user):
if not has_manufacturing_access(request.user):
messages.error(request, 'You do not have permission to delete manufacturing orders.')
return redirect('manufacture:manufacture_list')
@ -65,8 +110,171 @@ def manufacture_delete(request, pk):
return render(request, 'manufacture/manufacture_confirm_delete.html', {'manufacturing_order': manufacturing_order})
@login_required
def bom_create(request):
"""Create a new bill of materials entry"""
if not has_manufacturing_access(request.user):
messages.error(request, 'You do not have permission to create bill of materials entries.')
return redirect('manufacture:bom_list')
if request.method == 'POST':
main_form = MultipleBillOfMaterialsForm(request.POST)
component_formset = BOMComponentFormSet(request.POST)
if main_form.is_valid() and component_formset.is_valid():
manufactured_product = main_form.cleaned_data['manufactured_product']
# Save all component entries
saved_components = []
for component_form in component_formset:
# Skip empty forms
if not component_form.cleaned_data:
continue
component = component_form.cleaned_data['component']
quantity = component_form.cleaned_data['quantity']
unit = component_form.cleaned_data['unit']
# Create BOM entry
bom = BillOfMaterials(
manufactured_product=manufactured_product,
component=component,
quantity=quantity,
unit=unit
)
bom.save()
saved_components.append(bom)
messages.success(request, f'Bill of Materials for "{manufactured_product.name}" with {len(saved_components)} components created successfully!')
return redirect('manufacture:bom_list')
else:
main_form = MultipleBillOfMaterialsForm()
component_formset = BOMComponentFormSet()
return render(request, 'manufacture/bom_form.html', {
'main_form': main_form,
'component_formset': component_formset,
'title': 'Create Bill of Materials'
})
@login_required
def bom_edit(request, pk):
"""Edit an existing bill of materials entry"""
if not has_manufacturing_access(request.user):
messages.error(request, 'You do not have permission to edit bill of materials.')
return redirect('manufacture:bom_list')
# Get the BOM entry to edit
bom = get_object_or_404(BillOfMaterials, pk=pk)
if request.method == 'POST':
main_form = MultipleBillOfMaterialsForm(request.POST)
component_formset = BOMComponentFormSet(request.POST)
if main_form.is_valid() and component_formset.is_valid():
manufactured_product = main_form.cleaned_data['manufactured_product']
# Delete existing BOM entries for this manufactured product
BillOfMaterials.objects.filter(manufactured_product=manufactured_product).delete()
# Save all component entries
saved_components = []
for component_form in component_formset:
# Skip empty forms
if not component_form.cleaned_data:
continue
component = component_form.cleaned_data['component']
quantity = component_form.cleaned_data['quantity']
unit = component_form.cleaned_data['unit']
# Create BOM entry
bom_entry = BillOfMaterials(
manufactured_product=manufactured_product,
component=component,
quantity=quantity,
unit=unit
)
bom_entry.save()
saved_components.append(bom_entry)
messages.success(request, f'Bill of Materials for "{manufactured_product.name}" with {len(saved_components)} components updated successfully!')
return redirect('manufacture:bom_list')
else:
# Pre-populate the form with existing components
main_form = MultipleBillOfMaterialsForm(initial={'manufactured_product': bom.manufactured_product})
# Get all existing components for this manufactured product
existing_components = BillOfMaterials.objects.filter(manufactured_product=bom.manufactured_product)
# Create initial data for the formset
initial_data = []
for existing_bom in existing_components:
initial_data.append({
'component': existing_bom.component,
'quantity': existing_bom.quantity,
'unit': existing_bom.unit
})
# Add extra empty forms if needed
while len(initial_data) < 1:
initial_data.append({})
component_formset = BOMComponentFormSet(initial=initial_data)
return render(request, 'manufacture/bom_form.html', {
'main_form': main_form,
'component_formset': component_formset,
'title': 'Edit Bill of Materials',
'bom': bom
})
@login_required
def bom_delete(request, pk):
"""Delete a bill of materials entry"""
if not has_manufacturing_access(request.user):
messages.error(request, 'You do not have permission to delete bill of materials entries.')
return redirect('manufacture:bom_list')
bom = get_object_or_404(BillOfMaterials, pk=pk)
if request.method == 'POST':
manufactured_product_name = bom.manufactured_product.name
bom.delete()
messages.success(request, f'Bill of Materials entry for "{manufactured_product_name}" deleted successfully!')
return redirect('manufacture:bom_list')
return render(request, 'manufacture/bom_confirm_delete.html', {'bom': bom})
@method_decorator(login_required, name='dispatch')
class ManufactureDetailView(DetailView):
model = ManufacturingOrder
template_name = 'manufacture/manufacture_detail.html'
context_object_name = 'manufacturing_order'
@method_decorator(login_required, name='dispatch')
class BillOfMaterialsDetailView(DetailView):
model = BillOfMaterials
template_name = 'manufacture/bom_detail.html'
context_object_name = 'bom'
@login_required
def get_product_info(request, product_id):
"""Return product information including unit as JSON"""
if not has_manufacturing_access(request.user):
return JsonResponse({'error': 'Permission denied'}, status=403)
try:
product = Product.objects.get(id=product_id)
return JsonResponse({
'id': product.id,
'name': product.name,
'unit': product.unit,
'unit_display': product.get_unit_display()
})
except Product.DoesNotExist:
return JsonResponse({'error': 'Product not found'}, status=404)

View File

@ -69,10 +69,22 @@ TEMPLATES = [
WSGI_APPLICATION = 'manufacture_app.wsgi.application'
# Database
# When running as executable, database should be in the same directory as the executable
import sys
import os
# Check if we're running as a PyInstaller executable
if getattr(sys, 'frozen', False):
# Running as compiled executable
BASE_DB_DIR = Path(os.path.dirname(sys.executable))
else:
# Running as script
BASE_DB_DIR = BASE_DIR
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
'NAME': BASE_DB_DIR / 'db.sqlite3',
}
}

View File

@ -8,9 +8,9 @@ class PurchaseOrderItemInline(admin.TabularInline):
@admin.register(Supplier)
class SupplierAdmin(admin.ModelAdmin):
list_display = ('name', 'code', 'contact_person', 'email', 'phone',
'rating', 'is_active', 'created_at')
list_filter = ('is_active', 'rating', 'created_at')
list_display = ('name', 'code', 'contact_person', 'email', 'phone',
'is_active', 'created_at')
list_filter = ('is_active', 'created_at')
search_fields = ('name', 'code', 'contact_person', 'email', 'phone')
ordering = ('name',)
@ -18,7 +18,7 @@ class SupplierAdmin(admin.ModelAdmin):
('Basic Information', {'fields': ('name', 'code', 'contact_person', 'email', 'phone')}),
('Address', {'fields': ('address',)}),
('Business Information', {'fields': ('tax_id', 'payment_terms', 'credit_limit')}),
('Status', {'fields': ('is_active', 'rating')}),
('Status', {'fields': ('is_active',)}),
)
@admin.register(PurchaseOrder)

View File

@ -1,6 +1,6 @@
from django import forms
from .models import PurchaseOrder, PurchaseOrderItem, Supplier
from inventory.models import Product
from .models import PurchaseOrder, PurchaseOrderItem
from inventory.models import Product, Supplier
class PurchaseOrderForm(forms.ModelForm):
"""Form for creating and editing purchase orders"""
@ -17,7 +17,12 @@ class PurchaseOrderForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Add Bootstrap classes
# Update the supplier field queryset to only show active suppliers
from inventory.models import Supplier
self.fields['supplier'].queryset = Supplier.objects.filter(is_active=True).order_by('name')
self.fields['supplier'].widget.attrs.update({'class': 'form-control'})
# Add Bootstrap classes to other fields
for field in self.fields.values():
if isinstance(field.widget, (forms.CheckboxInput, forms.RadioSelect)):
field.widget.attrs.update({'class': 'form-check-input'})
@ -25,8 +30,10 @@ class PurchaseOrderForm(forms.ModelForm):
field.widget.attrs.update({'class': 'form-control'})
elif isinstance(field.widget, forms.DateInput):
field.widget.attrs.update({'class': 'form-control'})
else:
elif not isinstance(field.widget, forms.Select): # Skip Select widgets as we already handled them
field.widget.attrs.update({'class': 'form-control'})
# Removed _get_supplier_choices method as it's no longer needed
class PurchaseOrderItemForm(forms.ModelForm):
"""Form for purchase order items"""

View File

@ -0,0 +1,23 @@
# Generated by Django 4.2.7 on 2025-08-19 09:11
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('inventory', '0003_remove_supplier_rating'),
('purchase', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='purchaseorder',
name='supplier',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='purchase_orders', to='inventory.supplier'),
),
migrations.DeleteModel(
name='Supplier',
),
]

View File

@ -2,52 +2,9 @@ from django.db import models
from django.core.validators import MinValueValidator
from django.utils.translation import gettext_lazy as _
from decimal import Decimal
from inventory.models import Supplier
class Supplier(models.Model):
"""Supplier information"""
name = models.CharField(max_length=200)
code = models.CharField(max_length=50, unique=True, help_text='Unique supplier code')
contact_person = models.CharField(max_length=100, blank=True)
email = models.EmailField(blank=True)
phone = models.CharField(max_length=20, blank=True)
address = models.TextField(blank=True)
# Business information
tax_id = models.CharField(max_length=50, blank=True)
payment_terms = models.CharField(max_length=100, blank=True, help_text='e.g., Net 30, Net 60')
credit_limit = models.DecimalField(
max_digits=12,
decimal_places=2,
default=0,
validators=[MinValueValidator(Decimal('0'))]
)
# Status
is_active = models.BooleanField(default=True)
rating = models.IntegerField(
choices=[(i, i) for i in range(1, 6)],
default=3,
help_text='Supplier rating from 1-5'
)
# Timestamps
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['name']
verbose_name = _('Supplier')
verbose_name_plural = _('Suppliers')
def __str__(self):
return f"{self.name} ({self.code})"
def get_total_purchases(self):
"""Get total purchases from this supplier"""
return self.purchase_orders.aggregate(
total=models.Sum('total_amount')
)['total'] or Decimal('0')
# Supplier model removed - using inventory.models.Supplier instead
class PurchaseOrder(models.Model):
"""Purchase order model"""
@ -61,7 +18,7 @@ class PurchaseOrder(models.Model):
]
order_number = models.CharField(max_length=50, unique=True, help_text='Unique purchase order number')
supplier = models.ForeignKey(Supplier, on_delete=models.CASCADE, related_name='purchase_orders')
supplier = models.ForeignKey('inventory.Supplier', on_delete=models.CASCADE, related_name='purchase_orders')
date = models.DateField()
expected_delivery_date = models.DateField(blank=True, null=True)
status = models.CharField(max_length=20, choices=ORDER_STATUS, default='draft')

View File

@ -1,5 +1,6 @@
from django.contrib import admin
from .models import Customer, SaleOrder, SaleOrderItem, DeliveryNote
from .models import SaleOrder, SaleOrderItem, DeliveryNote
from inventory.models import Customer
class SaleOrderItemInline(admin.TabularInline):
model = SaleOrderItem
@ -8,9 +9,9 @@ class SaleOrderItemInline(admin.TabularInline):
@admin.register(Customer)
class CustomerAdmin(admin.ModelAdmin):
list_display = ('name', 'code', 'customer_type', 'contact_person', 'email', 'phone',
'rating', 'is_active', 'created_at')
list_filter = ('customer_type', 'is_active', 'rating', 'created_at')
list_display = ('name', 'code', 'customer_type', 'contact_person', 'email', 'phone',
'is_active', 'created_at')
list_filter = ('customer_type', 'is_active', 'created_at')
search_fields = ('name', 'code', 'contact_person', 'email', 'phone')
ordering = ('name',)
@ -18,7 +19,7 @@ class CustomerAdmin(admin.ModelAdmin):
('Basic Information', {'fields': ('name', 'code', 'customer_type', 'contact_person', 'email', 'phone')}),
('Address', {'fields': ('address',)}),
('Business Information', {'fields': ('tax_id', 'payment_terms', 'credit_limit')}),
('Status', {'fields': ('is_active', 'rating')}),
('Status', {'fields': ('is_active',)}),
)
@admin.register(SaleOrder)

View File

@ -1,6 +1,6 @@
from django import forms
from .models import SaleOrder, SaleOrderItem, Customer
from inventory.models import Product
from .models import SaleOrder, SaleOrderItem
from inventory.models import Product, Customer
class SaleOrderForm(forms.ModelForm):
"""Form for creating and editing sales orders"""
@ -17,7 +17,12 @@ class SaleOrderForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Add Bootstrap classes
# Update the customer field queryset to only show active customers
from inventory.models import Customer
self.fields['customer'].queryset = Customer.objects.filter(is_active=True).order_by('name')
self.fields['customer'].widget.attrs.update({'class': 'form-control'})
# Add Bootstrap classes to other fields
for field in self.fields.values():
if isinstance(field.widget, (forms.CheckboxInput, forms.RadioSelect)):
field.widget.attrs.update({'class': 'form-check-input'})
@ -25,8 +30,10 @@ class SaleOrderForm(forms.ModelForm):
field.widget.attrs.update({'class': 'form-control'})
elif isinstance(field.widget, forms.DateInput):
field.widget.attrs.update({'class': 'form-control'})
else:
elif not isinstance(field.widget, forms.Select): # Skip Select widgets as we already handled them
field.widget.attrs.update({'class': 'form-control'})
# Removed _get_customer_choices method as it's no longer needed
class SaleOrderItemForm(forms.ModelForm):
"""Form for sales order items"""

View File

@ -0,0 +1,23 @@
# Generated by Django 4.2.7 on 2025-08-19 09:11
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('inventory', '0003_remove_supplier_rating'),
('sales', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='saleorder',
name='customer',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sale_orders', to='inventory.customer'),
),
migrations.DeleteModel(
name='Customer',
),
]

View File

@ -2,62 +2,9 @@ from django.db import models
from django.core.validators import MinValueValidator
from django.utils.translation import gettext_lazy as _
from decimal import Decimal
from inventory.models import Customer
class Customer(models.Model):
"""Customer information"""
CUSTOMER_TYPE_CHOICES = [
('retail', 'Retail'),
('wholesale', 'Wholesale'),
('distributor', 'Distributor'),
('corporate', 'Corporate'),
]
name = models.CharField(max_length=200)
code = models.CharField(max_length=50, unique=True, help_text='Unique customer code')
customer_type = models.CharField(max_length=20, choices=CUSTOMER_TYPE_CHOICES, default='retail')
# Contact information
contact_person = models.CharField(max_length=100, blank=True)
email = models.EmailField(blank=True)
phone = models.CharField(max_length=20, blank=True)
address = models.TextField(blank=True)
# Business information
tax_id = models.CharField(max_length=50, blank=True)
payment_terms = models.CharField(max_length=100, blank=True, help_text='e.g., Net 30, Net 60')
credit_limit = models.DecimalField(
max_digits=12,
decimal_places=2,
default=0,
validators=[MinValueValidator(Decimal('0'))]
)
# Status
is_active = models.BooleanField(default=True)
rating = models.IntegerField(
choices=[(i, i) for i in range(1, 6)],
default=3,
help_text='Customer rating from 1-5'
)
# Timestamps
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['name']
verbose_name = _('Customer')
verbose_name_plural = _('Customers')
def __str__(self):
return f"{self.name} ({self.code})"
def get_total_sales(self):
"""Get total sales to this customer"""
return self.sale_orders.aggregate(
total=models.Sum('total_amount')
)['total'] or Decimal('0')
# Customer model removed - using inventory.models.Customer instead
class SaleOrder(models.Model):
"""Sales order model"""
@ -71,7 +18,7 @@ class SaleOrder(models.Model):
]
order_number = models.CharField(max_length=50, unique=True, help_text='Unique sales order number')
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name='sale_orders')
customer = models.ForeignKey('inventory.Customer', on_delete=models.CASCADE, related_name='sale_orders')
date = models.DateField()
expected_delivery_date = models.DateField(blank=True, null=True)
status = models.CharField(max_length=20, choices=ORDER_STATUS, default='draft')

View File

@ -1,6 +1,146 @@
import os
from django.core.management import execute_from_command_line
import sys
from pathlib import Path
def setup_django():
"""Set up Django environment"""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'manufacture_app.settings')
import django
django.setup()
def get_db_path():
"""Get the database path based on execution mode"""
# Check if we're running as a PyInstaller executable
if getattr(sys, 'frozen', False):
# Running as compiled executable
base_db_dir = Path(os.path.dirname(sys.executable))
print(f"Running as compiled executable. Database directory: {base_db_dir}")
else:
# Running as script
# Use the same logic as Django settings to determine the base directory
# BASE_DIR is the parent of the settings module directory
base_db_dir = Path(os.path.dirname(os.path.abspath(__file__))).parent
print(f"Running as script. Database directory: {base_db_dir}")
db_path = base_db_dir / 'db.sqlite3'
print(f"Database path: {db_path}")
# If running as script and the database doesn't exist in the project root,
# check if there's one in the current working directory
if not getattr(sys, 'frozen', False) and not db_path.exists():
cwd_db_path = Path.cwd() / 'db.sqlite3'
if cwd_db_path.exists():
print(f"Using database from current working directory: {cwd_db_path}")
return cwd_db_path
return db_path
def db_initialized():
"""Check if the database is initialized by checking if tables exist"""
setup_django()
try:
from django.db import connection
from django.db.utils import OperationalError
# Try to connect to the database
connection.ensure_connection()
print("Database connection established")
# Check if any tables exist
with connection.cursor() as cursor:
# For SQLite, we can check the sqlite_master table
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='django_migrations'")
result = cursor.fetchone()
print(f"django_migrations table exists: {result is not None}")
return result is not None
except OperationalError as e:
print(f"Database connection error: {e}")
return False
except Exception as e:
print(f"Unexpected error checking database: {e}")
import traceback
traceback.print_exc()
return False
def create_default_superuser():
"""Create a default superuser account"""
setup_django()
try:
from django.contrib.auth import get_user_model
User = get_user_model()
# Check if superuser already exists
if User.objects.filter(is_superuser=True).exists():
print("Superuser already exists. Skipping creation.")
return True
# Create default superuser
superuser = User.objects.create_superuser(
username='admin',
email='admin@example.com',
password='admin123'
)
print("Default superuser created:")
print(" Username: admin")
print(" Password: admin123")
print(" Email: admin@example.com")
print("Please change the password after first login!")
return True
except Exception as e:
print(f"Failed to create default superuser: {e}")
import traceback
traceback.print_exc()
return False
def initialize_database():
"""Initialize the database with migrations and initial data"""
print("Initializing database...")
setup_django()
# Check database path from Django settings
from django.conf import settings
print(f"Django database path: {settings.DATABASES['default']['NAME']}")
try:
from django.core.management import call_command
# Run migrations
print("Applying database migrations...")
call_command('migrate', verbosity=1)
# Create default groups
print("Creating default user groups...")
call_command('create_default_groups', verbosity=1)
# Create default superuser
print("Creating default superuser...")
create_default_superuser()
print("Database initialization completed successfully.")
return True
except Exception as e:
print(f"Database initialization failed: {e}")
import traceback
traceback.print_exc()
return False
if __name__ == '__main__':
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'manufacture_app.settings')
# Check if database exists and is initialized
db_path = get_db_path()
print(f"Database file exists: {db_path.exists()}")
if not db_path.exists() or not db_initialized():
print("Database not found or not initialized. Initializing now...")
if not initialize_database():
print("Failed to initialize database. Exiting.")
sys.exit(1)
print("Database ready.")
# Setup Django for running the server
setup_django()
# Start the server
from django.core.management import execute_from_command_line
execute_from_command_line(['manage.py', 'runserver', '--noreload'])

View File

@ -59,9 +59,70 @@ for app in django_apps:
datas.append((str(file_path), str(relative_path.parent)))
# Add the database
db_file = base_dir / 'db.sqlite3'
if db_file.exists():
datas.append((str(db_file), '.'))
# Database is now kept external to the executable
# db_file = base_dir / 'db.sqlite3'
# if db_file.exists():
# datas.append((str(db_file), '.'))
# Add crispy forms templates
try:
import crispy_forms
crispy_forms_path = Path(crispy_forms.__file__).parent
crispy_templates_dir = crispy_forms_path / 'templates'
if crispy_templates_dir.exists():
for root, dirs, files in os.walk(crispy_templates_dir):
for file in files:
file_path = Path(root) / file
relative_path = file_path.relative_to(crispy_forms_path)
datas.append((str(file_path), str(relative_path.parent)))
except ImportError:
pass
# Add crispy bootstrap5 templates
try:
import crispy_bootstrap5
crispy_bootstrap5_path = Path(crispy_bootstrap5.__file__).parent
crispy_bootstrap5_templates_dir = crispy_bootstrap5_path / 'templates'
if crispy_bootstrap5_templates_dir.exists():
for root, dirs, files in os.walk(crispy_bootstrap5_templates_dir):
for file in files:
file_path = Path(root) / file
relative_path = file_path.relative_to(crispy_bootstrap5_path)
datas.append((str(file_path), str(relative_path.parent)))
except ImportError:
pass
# Add Django templates
try:
import django
django_path = Path(django.__file__).parent
django_templates_dir = django_path / 'forms' / 'templates'
if django_templates_dir.exists():
for root, dirs, files in os.walk(django_templates_dir):
for file in files:
file_path = Path(root) / file
relative_path = file_path.relative_to(django_path)
datas.append((str(file_path), str(relative_path.parent)))
except ImportError:
pass
# Add more Django templates
try:
import django
django_path = Path(django.__file__).parent
django_contrib_dir = django_path / 'contrib'
if django_contrib_dir.exists():
for root, dirs, files in os.walk(django_contrib_dir):
# Look for templates directories
if 'templates' in dirs:
templates_dir = Path(root) / 'templates'
for t_root, t_dirs, t_files in os.walk(templates_dir):
for file in t_files:
file_path = Path(t_root) / file
relative_path = file_path.relative_to(django_path)
datas.append((str(file_path), str(relative_path.parent)))
except ImportError:
pass
# Hidden imports for Django
hiddenimports = [
@ -87,6 +148,17 @@ hiddenimports = [
'django.contrib.sessions.apps',
'django.contrib.messages.apps',
'django.contrib.staticfiles.apps',
# Additional imports for form rendering
'django.forms',
'django.forms.widgets',
'django.forms.models',
'django.forms.fields',
'django.forms.boundfield',
# Additional imports for model relationships
'django.db.models',
'django.db.models.fields',
'django.db.models.fields.related',
'django.db.models.fields.reverse_related',
]
a = Analysis(

View File

@ -129,6 +129,12 @@
Manufacturing
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'manufacture:bom_list' %}">
<i class="bi bi-list-check"></i>
Bill of Materials
</a>
</li>
{% endif %}
{% if user.has_inventory_permission %}

View File

@ -76,46 +76,45 @@
</div>
<!-- Restore Database -->
<div class="row">
<!-- Change Password -->
<div class="row mt-4">
<div class="col-lg-8">
<div class="card">
<div class="card-header">
<h6 class="m-0 font-weight-bold text-warning">
<i class="bi bi-upload me-2"></i>
Restore Database
<h6 class="m-0 font-weight-bold text-info">
<i class="bi bi-key me-2"></i>
Change Password
</h6>
</div>
<div class="card-body">
<div class="alert alert-warning">
<i class="bi bi-exclamation-triangle me-2"></i>
<strong>Warning:</strong> Restoring a database will overwrite the current database.
A safety backup of the current database will be created automatically before restoration.
<div class="alert alert-info">
<i class="bi bi-info-circle me-2"></i>
<strong>Info:</strong> Change the password for any user account in the system.
</div>
<form method="post" action="{% url 'core:restore_database' %}" enctype="multipart/form-data">
<form method="post" action="{% url 'core:change_password' %}">
{% csrf_token %}
<div class="mb-3">
<label for="backup_file" class="form-label">Select Backup File</label>
<input type="file" class="form-control" id="backup_file" name="backup_file"
accept=".sqlite3,.db" required>
<label for="username" class="form-label">Username</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="mb-3">
<label for="new_password" class="form-label">New Password</label>
<input type="password" class="form-control" id="new_password" name="new_password" required>
<div class="form-text">
Only SQLite database files (.sqlite3, .db) are supported.
Password must be at least 8 characters long.
</div>
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="confirm_restore" required>
<label class="form-check-label" for="confirm_restore">
I understand that this will overwrite the current database
</label>
</div>
<label for="confirm_password" class="form-label">Confirm New Password</label>
<input type="password" class="form-control" id="confirm_password" name="confirm_password" required>
</div>
<button type="submit" class="btn btn-warning" id="restore_btn" disabled>
<i class="bi bi-upload me-2"></i>
Restore Database
<button type="submit" class="btn btn-info">
<i class="bi bi-key me-2"></i>
Change Password
</button>
</form>
</div>

View File

@ -0,0 +1,49 @@
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block title %}Simple Test{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">
<i class="bi bi-bug me-2"></i>
Simple Test
</h1>
</div>
<div class="row">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">Purchase Order Form</h5>
</div>
<div class="card-body">
<form>
<div class="mb-3">
<label class="form-label">Supplier:</label>
{{ purchase_form.supplier }}
</div>
</form>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">Sales Order Form</h5>
</div>
<div class="card-body">
<form>
<div class="mb-3">
<label class="form-label">Customer:</label>
{{ sales_form.customer }}
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,109 @@
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block title %}Test Form{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">
<i class="bi bi-bug me-2"></i>
Test Form
</h1>
</div>
<div class="row">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">Purchase Order Form</h5>
</div>
<div class="card-body">
<form>
<div class="mb-3">
<label class="form-label">Supplier Field:</label>
{{ purchase_form.supplier|as_crispy_field }}
</div>
<div class="mb-3">
<label class="form-label">Date Field:</label>
{{ purchase_form.date|as_crispy_field }}
</div>
</form>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">Sales Order Form</h5>
</div>
<div class="card-body">
<form>
<div class="mb-3">
<label class="form-label">Customer Field:</label>
{{ sales_form.customer|as_crispy_field }}
</div>
<div class="mb-3">
<label class="form-label">Date Field:</label>
{{ sales_form.date|as_crispy_field }}
</div>
</form>
</div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">Debug Information</h5>
</div>
<div class="card-body">
<h6>Suppliers in Database:</h6>
<ul>
{% for supplier in suppliers %}
<li>{{ supplier.name }} ({{ supplier.code }})</li>
{% empty %}
<li>No suppliers found</li>
{% endfor %}
</ul>
<h6>Customers in Database:</h6>
<ul>
{% for customer in customers %}
<li>{{ customer.name }} ({{ customer.code }})</li>
{% empty %}
<li>No customers found</li>
{% endfor %}
</ul>
<h6>Database Connectivity:</h6>
<p>Connected: {{ db_connected|yesno:"Yes,No" }}</p>
{% if db_error %}
<p>Error: {{ db_error }}</p>
{% endif %}
<h6>Database Information:</h6>
<p>Suppliers in database: {{ supplier_count }}</p>
<p>Customers in database: {{ customer_count }}</p>
{% if model_error %}
<p>Model error: {{ model_error }}</p>
{% endif %}
<h6>Purchase Form Debug Info:</h6>
<p>Field type: {{ purchase_form_debug.field_type }}</p>
<p>Choices count: {{ purchase_form_debug.choices_count }}</p>
<p>Choices: {{ purchase_form_debug.choices }}</p>
<h6>Sales Form Debug Info:</h6>
<p>Field type: {{ sales_form_debug.field_type }}</p>
<p>Choices count: {{ sales_form_debug.choices_count }}</p>
<p>Choices: {{ sales_form_debug.choices }}</p>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,4 +1,5 @@
{% extends 'base.html' %}
{% load manufacture_extras %}
{% block title %}Product - {{ product.name }}{% endblock %}
@ -110,11 +111,11 @@
<div class="card-body">
<div class="text-center mb-3">
<h6 class="text-muted">Cost Price</h6>
<h4 class="text-primary">Rp {{ product.cost_price|floatformat:0 }}</h4>
<h4 class="text-primary">Rp {{ product.cost_price|format_currency }}</h4>
</div>
<div class="text-center mb-3">
<h6 class="text-muted">Selling Price</h6>
<h4 class="text-success">Rp {{ product.selling_price|floatformat:0 }}</h4>
<h4 class="text-success">Rp {{ product.selling_price|format_currency }}</h4>
</div>
{% if product.selling_price > 0 and product.cost_price > 0 %}
<div class="text-center">

View File

@ -1,4 +1,5 @@
{% extends 'base.html' %}
{% load manufacture_extras %}
{% block title %}Inventory - Products{% endblock %}
@ -46,8 +47,8 @@
</span>
</td>
<td>{{ product.min_stock_level }} {{ product.unit }}</td>
<td>Rp {{ product.cost_price|floatformat:0 }}</td>
<td>Rp {{ product.selling_price|floatformat:0 }}</td>
<td>Rp {{ product.cost_price|format_currency }}</td>
<td>Rp {{ product.selling_price|format_currency }}</td>
<td>
<span class="badge bg-{% if product.is_active %}success{% else %}secondary{% endif %}">
{{ product.is_active|yesno:"Active,Inactive" }}

View File

@ -31,12 +31,7 @@
<div class="col-md-6">
<p><strong>Supplier Code:</strong> {{ supplier.code }}</p>
<p><strong>Name:</strong> {{ supplier.name }}</p>
<p><strong>Rating:</strong>
<span class="badge bg-{% if supplier.rating >= 4 %}success{% elif supplier.rating >= 3 %}warning{% else %}danger{% endif %}">
{{ supplier.rating }}/5
</span>
</p>
<p><strong>Status:</strong>
<p><strong>Status:</strong>
<span class="badge bg-{% if supplier.is_active %}success{% else %}secondary{% endif %}">
{{ supplier.is_active|yesno:"Active,Inactive" }}
</span>

View File

@ -44,9 +44,6 @@
<div class="col-md-6">
{{ form.phone|as_crispy_field }}
</div>
<div class="col-md-6">
{{ form.rating|as_crispy_field }}
</div>
</div>
<div class="row">
<div class="col-md-12">
@ -87,10 +84,6 @@
<i class="bi bi-info-circle text-primary me-2"></i>
Supplier code should be unique
</li>
<li class="mb-2">
<i class="bi bi-star text-warning me-2"></i>
Rate suppliers based on performance
</li>
<li class="mb-2">
<i class="bi bi-telephone text-info me-2"></i>
Keep contact information up to date

View File

@ -26,7 +26,6 @@
<th>Contact Person</th>
<th>Email</th>
<th>Phone</th>
<th>Rating</th>
<th>Credit Limit</th>
<th>Status</th>
<th>Actions</th>
@ -42,11 +41,6 @@
<td>{{ supplier.contact_person|default:"N/A" }}</td>
<td>{{ supplier.email|default:"N/A" }}</td>
<td>{{ supplier.phone|default:"N/A" }}</td>
<td>
<span class="badge bg-{% if supplier.rating >= 4 %}success{% elif supplier.rating >= 3 %}warning{% else %}danger{% endif %}">
{{ supplier.rating }}/5
</span>
</td>
<td>Rp {{ supplier.credit_limit|floatformat:0 }}</td>
<td>
<span class="badge bg-{% if supplier.is_active %}success{% else %}secondary{% endif %}">

View File

@ -0,0 +1,47 @@
{% extends 'base.html' %}
{% block title %}Delete BOM Entry{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 mb-0">
<i class="bi bi-trash me-2"></i>
Delete BOM Entry
</h1>
<a href="{% url 'manufacture:bom_list' %}" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left me-2"></i>
Back to BOM List
</a>
</div>
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card border-danger">
<div class="card-header bg-danger text-white">
<h5 class="card-title mb-0">
<i class="bi bi-exclamation-triangle me-2"></i>
Confirm Deletion
</h5>
</div>
<div class="card-body">
<p class="lead">Are you sure you want to delete the BOM entry for <strong>"{{ bom.manufactured_product.name }}"</strong> with component <strong>"{{ bom.component.name }}"</strong>?</p>
<p>This action cannot be undone.</p>
<form method="post">
{% csrf_token %}
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
<a href="{% url 'manufacture:bom_list' %}" class="btn btn-secondary me-md-2">
<i class="bi bi-x-circle me-2"></i>
Cancel
</a>
<button type="submit" class="btn btn-danger">
<i class="bi bi-trash me-2"></i>
Delete BOM Entry
</button>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,130 @@
{% extends 'base.html' %}
{% load manufacture_extras %}
{% block title %}BOM - {{ bom.manufactured_product.name }}{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 mb-0">
<i class="bi bi-list-check me-2"></i>
Bill of Materials
</h1>
<div>
<a href="{% url 'manufacture:bom_list' %}" class="btn btn-outline-secondary me-2">
<i class="bi bi-arrow-left me-2"></i>
Back to List
</a>
{% if user.is_superuser or user.is_staff %}
<a href="{% url 'manufacture:bom_edit' bom.pk %}" class="btn btn-primary">
<i class="bi bi-pencil me-2"></i>
Edit
</a>
{% endif %}
</div>
</div>
<div class="row">
<div class="col-md-8">
<div class="card mb-4">
<div class="card-header">
<h5 class="card-title mb-0">BOM Details</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<p><strong>Manufactured Product:</strong> {{ bom.manufactured_product.name }}</p>
<p><strong>Product Code:</strong> {{ bom.manufactured_product.code }}</p>
</div>
<div class="col-md-6">
<p><strong>Component:</strong> {{ bom.component.name }}</p>
<p><strong>Quantity:</strong> {{ bom.quantity|format_quantity }}</p>
<p><strong>Unit:</strong> {{ bom.unit|default:bom.component.unit }}</p>
</div>
</div>
</div>
<!-- Show all components for this manufactured product -->
{% with bom.manufactured_product.bom_manufactured.all as all_components %}
{% if all_components.count > 1 %}
<div class="card mb-4">
<div class="card-header">
<h5 class="card-title mb-0">All Components for {{ bom.manufactured_product.name }}</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead class="table-light">
<tr>
<th>Component</th>
<th>Quantity</th>
<th>Unit</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for component_bom in all_components %}
<tr>
<td>{{ component_bom.component.name }}</td>
<td>{{ component_bom.quantity|format_quantity }}</td>
<td>{{ component_bom.unit|default:component_bom.component.unit }}</td>
<td>
{% if component_bom.pk != bom.pk %}
<a href="{% url 'manufacture:bom_detail' component_bom.pk %}" class="btn btn-sm btn-outline-primary">
<i class="bi bi-eye"></i>
</a>
<a href="{% url 'manufacture:bom_edit' component_bom.pk %}" class="btn btn-sm btn-outline-warning ms-1">
<i class="bi bi-pencil"></i>
</a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endif %}
{% endwith %}
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">Cost Information</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<p><strong>Component Cost Price:</strong> Rp {{ bom.component.cost_price|format_currency }}</p>
</div>
<div class="col-md-6">
<p><strong>Total Component Cost:</strong> Rp {{ bom.get_total_component_cost|format_currency }}</p>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">Product Information</h5>
</div>
<div class="card-body">
<p><strong>Component Code:</strong> {{ bom.component.code }}</p>
<p><strong>Component Category:</strong> {{ bom.component.category.name|default:"N/A" }}</p>
<p><strong>Component Current Stock:</strong> {{ bom.component.current_stock }}</p>
<p><strong>Component Unit:</strong> {{ bom.component.unit }}</p>
</div>
</div>
<div class="card mt-3">
<div class="card-header">
<h5 class="card-title mb-0">Timestamps</h5>
</div>
<div class="card-body">
<p><strong>Created:</strong> {{ bom.created_at|date:"d/m/Y H:i" }}</p>
<p><strong>Updated:</strong> {{ bom.updated_at|date:"d/m/Y H:i" }}</p>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,338 @@
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 mb-0">
<i class="bi bi-list-check me-2"></i>
{{ title }}
</h1>
<a href="{% url 'manufacture:bom_list' %}" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left me-2"></i>
Back to BOM List
</a>
</div>
<div class="row">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">Bill of Materials Information</h5>
</div>
<div class="card-body">
<form method="post" id="bom-form">
{% csrf_token %}
{# Use dynamic form for both creation and editing #}
<div class="row">
<div class="col-md-12">
{% if main_form %}
{{ main_form.manufactured_product|as_crispy_field }}
{% else %}
{{ form.manufactured_product|as_crispy_field }}
{% endif %}
</div>
</div>
<h5 class="mt-4 mb-3">Components</h5>
{% if component_formset %}
{{ component_formset.management_form }}
<div id="component-forms-container">
{% for component_form in component_formset %}
<div class="card mb-3 component-form">
<div class="card-header d-flex justify-content-between align-items-center">
<h6 class="card-title mb-0">Component #{{ forloop.counter }}</h6>
{% if forloop.counter > 1 %}
<button type="button" class="btn btn-sm btn-outline-danger remove-component">
<i class="bi bi-trash"></i> Remove
</button>
{% endif %}
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
{{ component_form.component|as_crispy_field }}
</div>
<div class="col-md-6">
{{ component_form.quantity|as_crispy_field }}
</div>
<div class="row">
<div class="col-md-6">
{{ component_form.unit|as_crispy_field }}
</div>
</div>
</div>
</div>
{% endfor %}
</div>
<div class="mb-3">
<button type="button" id="add-component" class="btn btn-outline-primary">
<i class="bi bi-plus-circle me-2"></i>
Add Another Component
</button>
</div>
{% else %}
{# Single BOM form for backward compatibility #}
<div class="row">
<div class="col-md-6">
{{ form.component|as_crispy_field }}
</div>
<div class="col-md-6">
{{ form.quantity|as_crispy_field }}
</div>
<div class="row">
<div class="col-md-6">
{{ form.unit|as_crispy_field }}
</div>
</div>
{% endif %}
<div class="mt-3">
<button type="submit" class="btn btn-primary">
<i class="bi bi-check-circle me-2"></i>
Save BOM Entry
</button>
<a href="{% url 'manufacture:bom_list' %}" class="btn btn-outline-secondary ms-2">
Cancel
</a>
</div>
</form>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">BOM Tips</h5>
</div>
<div class="card-body">
<ul class="list-unstyled mb-0">
<li class="mb-2">
<i class="bi bi-info-circle text-primary me-2"></i>
Select the manufactured product and component
</li>
<li class="mb-2">
<i class="bi bi-calculator text-success me-2"></i>
Enter the exact quantity needed
</li>
<li class="mb-2">
<i class="bi bi-rulers text-info me-2"></i>
Unit of measurement is automatically populated
</li>
{% if component_formset %}
<li class="mb-2">
<i class="bi bi-plus-circle text-warning me-2"></i>
Add multiple components dynamically
</li>
<li class="mb-2">
<i class="bi bi-dash-circle text-danger me-2"></i>
Remove components you don't need
</li>
{% endif %}
</ul>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
document.addEventListener('DOMContentLoaded', function() {
// Get CSRF token
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
// Function to update unit field based on selected component
function updateUnitField(componentSelect, unitInput) {
const productId = componentSelect.value;
if (!productId) {
unitInput.value = '';
return;
}
// Make AJAX call to get product info
fetch(`/manufacture/bom/product/${productId}/info/`, {
method: 'GET',
headers: {
'X-CSRFToken': getCookie('csrftoken'),
'Content-Type': 'application/json',
},
credentials: 'same-origin'
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
if (data.unit) {
unitInput.value = data.unit;
} else {
unitInput.value = '';
}
})
.catch(error => {
console.error('Error fetching product info:', error);
unitInput.value = '';
});
}
// Check if we have the dynamic form
if (document.getElementById('component-forms-container')) {
// Add component button
document.getElementById('add-component').addEventListener('click', function() {
var container = document.getElementById('component-forms-container');
var formCount = container.querySelectorAll('.component-form').length;
var totalForms = document.getElementById('id_component-TOTAL_FORMS');
// Clone the first form as a template
var firstForm = container.querySelector('.component-form');
var newForm = firstForm.cloneNode(true);
// Update form indices
var newFormIndex = formCount;
var formRegex = /component-\d+/g;
// Update all input names and IDs
newForm.innerHTML = newForm.innerHTML.replace(formRegex, 'component-' + newFormIndex);
// Clear input values
var inputs = newForm.querySelectorAll('input, select, textarea');
inputs.forEach(function(input) {
if (input.type === 'checkbox' || input.type === 'radio') {
input.checked = false;
} else if (input.type !== 'hidden') {
input.value = '';
}
});
// Update header
var header = newForm.querySelector('.card-header h6');
header.textContent = 'Component #' + (newFormIndex + 1);
// Add remove button if it doesn't exist
var removeButtonContainer = newForm.querySelector('.card-header');
if (!removeButtonContainer.querySelector('.remove-component')) {
var removeButton = document.createElement('button');
removeButton.type = 'button';
removeButton.className = 'btn btn-sm btn-outline-danger remove-component';
removeButton.innerHTML = '<i class="bi bi-trash"></i> Remove';
removeButtonContainer.appendChild(removeButton);
}
// Add event listener to the new remove button
var newRemoveButton = newForm.querySelector('.remove-component');
newRemoveButton.addEventListener('click', function() {
newForm.remove();
updateFormIndices();
});
// Add event listener to the new component select
var newComponentSelect = newForm.querySelector('select[id^="id_component-"][id$="-component"]');
var newUnitInput = newForm.querySelector('input[id^="id_component-"][id$="-unit"]');
if (newComponentSelect && newUnitInput) {
newComponentSelect.addEventListener('change', function() {
updateUnitField(this, newUnitInput);
});
}
// Append new form
container.appendChild(newForm);
// Update total forms count
totalForms.value = formCount + 1;
});
// Remove component buttons
document.querySelectorAll('.remove-component').forEach(function(button) {
button.addEventListener('click', function() {
var form = this.closest('.component-form');
form.remove();
updateFormIndices();
});
});
// Add event listeners to existing component selects
document.querySelectorAll('.component-form select[id$="-component"]').forEach(function(select) {
var unitInput = select.closest('.component-form').querySelector('input[id$="-unit"]');
if (unitInput) {
select.addEventListener('change', function() {
updateUnitField(this, unitInput);
});
// Initialize unit field if component is already selected
if (select.value) {
updateUnitField(select, unitInput);
}
}
});
// Update form indices function
function updateFormIndices() {
var forms = document.querySelectorAll('.component-form');
var totalForms = document.getElementById('id_component-TOTAL_FORMS');
forms.forEach(function(form, index) {
// Update header
var header = form.querySelector('.card-header h6');
header.textContent = 'Component #' + (index + 1);
// Update form indices in names and IDs
var formRegex = /component-\d+/g;
form.innerHTML = form.innerHTML.replace(formRegex, 'component-' + index);
// Update event listeners for component selects
var componentSelect = form.querySelector('select[id$="-component"]');
var unitInput = form.querySelector('input[id$="-unit"]');
if (componentSelect && unitInput) {
// Remove existing event listeners by cloning
var newSelect = componentSelect.cloneNode(true);
componentSelect.parentNode.replaceChild(newSelect, componentSelect);
// Add new event listener
newSelect.addEventListener('change', function() {
updateUnitField(this, unitInput);
});
}
// Update remove button event listener
var removeButton = form.querySelector('.remove-component');
if (removeButton) {
// Remove existing event listener by cloning
var newRemoveButton = removeButton.cloneNode(true);
removeButton.parentNode.replaceChild(newRemoveButton, removeButton);
// Add new event listener
newRemoveButton.addEventListener('click', function() {
form.remove();
updateFormIndices();
});
}
});
// Update total forms count
totalForms.value = forms.length;
}
}
});
</script>
{% endblock %}

View File

@ -0,0 +1,95 @@
{% extends 'base.html' %}
{% load manufacture_extras %}
{% block title %}Bill of Materials{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 mb-0">
<i class="bi bi-list-check me-2"></i>
Bill of Materials
</h1>
<a href="{% url 'manufacture:bom_create' %}" class="btn btn-primary">
<i class="bi bi-plus-circle me-2"></i>
New BOM Entry (Multiple Components)
</a>
</div>
<div class="card">
<div class="card-body">
{% if boms %}
<div class="table-responsive">
<table class="table table-hover">
<thead class="table-light">
<tr>
<th>Manufactured Product</th>
<th>Component</th>
<th>Quantity</th>
<th>Unit</th>
<th>Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for bom in boms %}
<tr>
<td>
<strong>{{ bom.manufactured_product.name }}</strong>
<div class="small text-muted">{{ bom.manufactured_product.code }}</div>
</td>
<td>{{ bom.component.name }}</td>
<td>{{ bom.quantity|format_quantity }}</td>
<td>{{ bom.unit|default:bom.component.unit }}</td>
<td>{{ bom.created_at|date:"d/m/Y" }}</td>
<td>
<a href="{% url 'manufacture:bom_detail' bom.pk %}" class="btn btn-sm btn-outline-primary">
<i class="bi bi-eye"></i>
</a>
<a href="{% url 'manufacture:bom_edit' bom.pk %}" class="btn btn-sm btn-outline-warning ms-1">
<i class="bi bi-pencil"></i>
</a>
<a href="{% url 'manufacture:bom_delete' bom.pk %}" class="btn btn-sm btn-outline-danger ms-1">
<i class="bi bi-trash"></i>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% if is_paginated %}
<nav aria-label="Page navigation" class="mt-4">
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}">Previous</a>
</li>
{% endif %}
<li class="page-item active">
<span class="page-link">Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</span>
</li>
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}">Next</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
{% else %}
<div class="text-center py-5">
<i class="bi bi-list-check display-1 text-muted"></i>
<h4 class="mt-3 text-muted">No Bill of Materials Entries</h4>
<p class="text-muted">Start by creating your first BOM entry.</p>
<a href="{% url 'manufacture:bom_create' %}" class="btn btn-primary">
<i class="bi bi-plus-circle me-2"></i>
Create BOM Entry
</a>
</div>
{% endif %}
</div>
</div>
{% endblock %}

View File

@ -1,4 +1,5 @@
{% extends 'base.html' %}
{% load manufacture_extras %}
{% block title %}Manufacturing Order - {{ manufacturing_order.order_number }}{% endblock %}
@ -13,10 +14,12 @@
<i class="bi bi-arrow-left me-2"></i>
Back to List
</a>
<a href="#" class="btn btn-primary">
{% if user.is_superuser or user.is_staff %}
<a href="{% url 'manufacture:manufacture_edit' manufacturing_order.pk %}" class="btn btn-primary">
<i class="bi bi-pencil me-2"></i>
Edit
</a>
{% endif %}
</div>
</div>
@ -62,26 +65,26 @@
<div class="col-md-4">
<div class="text-center">
<h6 class="text-muted">Labor Cost</h6>
<h4 class="text-primary">Rp {{ manufacturing_order.labor_cost|floatformat:0 }}</h4>
<h4 class="text-primary">Rp {{ manufacturing_order.labor_cost|format_currency }}</h4>
</div>
</div>
<div class="col-md-4">
<div class="text-center">
<h6 class="text-muted">Overhead Cost</h6>
<h4 class="text-warning">Rp {{ manufacturing_order.overhead_cost|floatformat:0 }}</h4>
<h4 class="text-warning">Rp {{ manufacturing_order.overhead_cost|format_currency }}</h4>
</div>
</div>
<div class="col-md-4">
<div class="text-center">
<h6 class="text-muted">Total Cost</h6>
<h4 class="text-success">Rp {{ manufacturing_order.total_cost|floatformat:0 }}</h4>
<h4 class="text-success">Rp {{ manufacturing_order.total_cost|format_currency }}</h4>
</div>
</div>
</div>
<div class="text-center mt-3">
<h6 class="text-muted">Unit Cost</h6>
<h5 class="text-info">Rp {{ manufacturing_order.get_unit_cost|floatformat:0 }}</h5>
<h5 class="text-info">Rp {{ manufacturing_order.get_unit_cost|format_currency }}</h5>
</div>
</div>
</div>
@ -96,8 +99,8 @@
<p><strong>Product Code:</strong> {{ manufacturing_order.product.code }}</p>
<p><strong>Category:</strong> {{ manufacturing_order.product.category.name|default:"N/A" }}</p>
<p><strong>Current Stock:</strong> {{ manufacturing_order.product.current_stock }}</p>
<p><strong>Cost Price:</strong> Rp {{ manufacturing_order.product.cost_price|floatformat:0 }}</p>
<p><strong>Selling Price:</strong> Rp {{ manufacturing_order.product.selling_price|floatformat:0 }}</p>
<p><strong>Cost Price:</strong> Rp {{ manufacturing_order.product.cost_price|format_currency }}</p>
<p><strong>Selling Price:</strong> Rp {{ manufacturing_order.product.selling_price|format_currency }}</p>
</div>
</div>

View File

@ -1,4 +1,5 @@
{% extends 'base.html' %}
{% load manufacture_extras %}
{% block title %}Manufacturing Orders{% endblock %}
@ -44,7 +45,7 @@
{{ order.get_status_display }}
</span>
</td>
<td>Rp {{ order.total_cost|floatformat:0 }}</td>
<td>Rp {{ order.total_cost|format_currency }}</td>
<td>
<a href="{% url 'manufacture:manufacture_detail' order.pk %}" class="btn btn-sm btn-outline-primary">
<i class="bi bi-eye"></i>

View File

@ -1,4 +1,5 @@
{% extends 'base.html' %}
{% load manufacture_extras %}
{% block title %}Purchase Order - {{ purchase_order.order_number }}{% endblock %}
@ -62,25 +63,25 @@
<div class="col-md-3">
<div class="text-center">
<h6 class="text-muted">Subtotal</h6>
<h4 class="text-primary">Rp {{ purchase_order.subtotal|floatformat:0 }}</h4>
<h4 class="text-primary">Rp {{ purchase_order.subtotal|format_currency }}</h4>
</div>
</div>
<div class="col-md-3">
<div class="text-center">
<h6 class="text-muted">Tax</h6>
<h4 class="text-warning">Rp {{ purchase_order.tax_amount|floatformat:0 }}</h4>
<h4 class="text-warning">Rp {{ purchase_order.tax_amount|format_currency }}</h4>
</div>
</div>
<div class="col-md-3">
<div class="text-center">
<h6 class="text-muted">Shipping</h6>
<h4 class="text-info">Rp {{ purchase_order.shipping_cost|floatformat:0 }}</h4>
<h4 class="text-info">Rp {{ purchase_order.shipping_cost|format_currency }}</h4>
</div>
</div>
<div class="col-md-3">
<div class="text-center">
<h6 class="text-muted">Total</h6>
<h4 class="text-success">Rp {{ purchase_order.total_amount|floatformat:0 }}</h4>
<h4 class="text-success">Rp {{ purchase_order.total_amount|format_currency }}</h4>
</div>
</div>
</div>
@ -103,7 +104,7 @@
{{ purchase_order.supplier.rating }}/5
</span>
</p>
<p><strong>Credit Limit:</strong> Rp {{ purchase_order.supplier.credit_limit|floatformat:0 }}</p>
<p><strong>Credit Limit:</strong> Rp {{ purchase_order.supplier.credit_limit|format_currency }}</p>
</div>
</div>

View File

@ -1,4 +1,5 @@
{% extends 'base.html' %}
{% load manufacture_extras %}
{% block title %}Purchase Orders{% endblock %}
@ -43,8 +44,8 @@
{{ order.get_status_display }}
</span>
</td>
<td>Rp {{ order.subtotal|floatformat:0 }}</td>
<td>Rp {{ order.total_amount|floatformat:0 }}</td>
<td>Rp {{ order.subtotal|format_currency }}</td>
<td>Rp {{ order.total_amount|format_currency }}</td>
<td>
<a href="{% url 'purchase:purchase_detail' order.pk %}" class="btn btn-sm btn-outline-primary">
<i class="bi bi-eye"></i>

View File

@ -1,4 +1,5 @@
{% extends 'base.html' %}
{% load manufacture_extras %}
{% block title %}Sales Order - {{ sale_order.order_number }}{% endblock %}
@ -62,25 +63,25 @@
<div class="col-md-3">
<div class="text-center">
<h6 class="text-muted">Subtotal</h6>
<h4 class="text-primary">Rp {{ sale_order.subtotal|floatformat:0 }}</h4>
<h4 class="text-primary">Rp {{ sale_order.subtotal|format_currency }}</h4>
</div>
</div>
<div class="col-md-3">
<div class="text-center">
<h6 class="text-muted">Tax</h6>
<h4 class="text-warning">Rp {{ sale_order.tax_amount|floatformat:0 }}</h4>
<h4 class="text-warning">Rp {{ sale_order.tax_amount|format_currency }}</h4>
</div>
</div>
<div class="col-md-3">
<div class="text-center">
<h6 class="text-muted">Discount</h6>
<h4 class="text-info">Rp {{ sale_order.discount_amount|floatformat:0 }}</h4>
<h4 class="text-info">Rp {{ sale_order.discount_amount|format_currency }}</h4>
</div>
</div>
<div class="col-md-3">
<div class="text-center">
<h6 class="text-muted">Total</h6>
<h4 class="text-success">Rp {{ sale_order.total_amount|floatformat:0 }}</h4>
<h4 class="text-success">Rp {{ sale_order.total_amount|format_currency }}</h4>
</div>
</div>
</div>
@ -99,7 +100,7 @@
<p><strong>Contact:</strong> {{ sale_order.customer.contact_person|default:"N/A" }}</p>
<p><strong>Email:</strong> {{ sale_order.customer.email|default:"N/A" }}</p>
<p><strong>Phone:</strong> {{ sale_order.customer.phone|default:"N/A" }}</p>
<p><strong>Credit Limit:</strong> Rp {{ sale_order.customer.credit_limit|floatformat:0 }}</p>
<p><strong>Credit Limit:</strong> Rp {{ sale_order.customer.credit_limit|format_currency }}</p>
</div>
</div>

View File

@ -1,4 +1,5 @@
{% extends 'base.html' %}
{% load manufacture_extras %}
{% block title %}Sales Orders{% endblock %}
@ -43,8 +44,8 @@
{{ order.get_status_display }}
</span>
</td>
<td>Rp {{ order.subtotal|floatformat:0 }}</td>
<td>Rp {{ order.total_amount|floatformat:0 }}</td>
<td>Rp {{ order.subtotal|format_currency }}</td>
<td>Rp {{ order.total_amount|format_currency }}</td>
<td>
<a href="{% url 'sales:sales_detail' order.pk %}" class="btn btn-sm btn-outline-primary">
<i class="bi bi-eye"></i>

View File

View File

@ -0,0 +1,57 @@
from django.core.management.base import BaseCommand
from django.contrib.auth import get_user_model
from django.contrib.auth.password_validation import validate_password
from django.core.exceptions import ValidationError
import getpass
User = get_user_model()
class Command(BaseCommand):
help = 'Change password for a user'
def add_arguments(self, parser):
parser.add_argument('username', nargs='?', type=str, help='Username to change password for')
def handle(self, *args, **options):
username = options['username']
if not username:
username = input("Enter username: ")
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
self.stdout.write(
self.style.ERROR(f'User "{username}" does not exist')
)
return
# Get new password
while True:
new_password = getpass.getpass("Enter new password: ")
confirm_password = getpass.getpass("Confirm new password: ")
if new_password != confirm_password:
self.stdout.write(
self.style.ERROR("Passwords do not match. Please try again.")
)
continue
# Validate password
try:
validate_password(new_password, user)
except ValidationError as e:
self.stdout.write(
self.style.ERROR(f"Password validation failed: {', '.join(e.messages)}")
)
continue
break
# Set new password
user.set_password(new_password)
user.save()
self.stdout.write(
self.style.SUCCESS(f'Successfully changed password for user "{username}"')
)

View File

@ -176,3 +176,14 @@ def group_delete(request, pk):
return redirect('users:group_list')
return render(request, 'users/group_confirm_delete.html', {'group': group})
def has_manufacturing_access(user):
"""Check if user has manufacturing access (either admin or manufacturing permission)"""
if user.is_superuser:
return True
if user.group and user.group.name == 'Administrators':
return True
if hasattr(user, 'has_manufacturing_permission') and user.has_manufacturing_permission():
return True
return False