From f71ce2f7df391c4fb071752230195bff7dfd3d75 Mon Sep 17 00:00:00 2001 From: Skia Date: Thu, 28 Jan 2016 16:53:37 +0100 Subject: [PATCH] Add first accounting implementation --- accounting/__init__.py | 0 accounting/admin.py | 12 +++ accounting/migrations/0001_initial.py | 72 ++++++++++++++ .../migrations/0002_auto_20160128_1538.py | 39 ++++++++ accounting/migrations/__init__.py | 0 accounting/models.py | 94 +++++++++++++++++++ accounting/tests.py | 3 + accounting/views.py | 3 + core/admin.py | 2 +- core/management/commands/setup.py | 16 +++- sith/settings.py | 4 + sith/urls.py | 4 +- 12 files changed, 245 insertions(+), 4 deletions(-) create mode 100644 accounting/__init__.py create mode 100644 accounting/admin.py create mode 100644 accounting/migrations/0001_initial.py create mode 100644 accounting/migrations/0002_auto_20160128_1538.py create mode 100644 accounting/migrations/__init__.py create mode 100644 accounting/models.py create mode 100644 accounting/tests.py create mode 100644 accounting/views.py diff --git a/accounting/__init__.py b/accounting/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/accounting/admin.py b/accounting/admin.py new file mode 100644 index 00000000..3c3e5f12 --- /dev/null +++ b/accounting/admin.py @@ -0,0 +1,12 @@ +from django.contrib import admin + +from accounting.models import * + + +admin.site.register(Customer) +admin.site.register(ProductType) +admin.site.register(Product) +admin.site.register(GeneralJournal) +admin.site.register(GenericInvoice) + + diff --git a/accounting/migrations/0001_initial.py b/accounting/migrations/0001_initial.py new file mode 100644 index 00000000..ad4ac36b --- /dev/null +++ b/accounting/migrations/0001_initial.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +from django.conf import settings +import accounting.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0005_auto_20160128_0842'), + ] + + operations = [ + migrations.CreateModel( + name='Customer', + fields=[ + ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL, primary_key=True, serialize=False)), + ('account_id', models.CharField(unique=True, verbose_name='account id', max_length=10)), + ], + options={ + 'verbose_name': 'customer', + 'verbose_name_plural': 'customers', + }, + ), + migrations.CreateModel( + name='GeneralJournal', + fields=[ + ('id', models.AutoField(verbose_name='ID', auto_created=True, primary_key=True, serialize=False)), + ('start_date', models.DateField(verbose_name='start date')), + ('end_date', models.DateField(null=True, verbose_name='end date', blank=True, default=None)), + ('name', models.CharField(verbose_name='name', max_length=30)), + ('closed', models.BooleanField(default=False, verbose_name='is closed')), + ], + ), + migrations.CreateModel( + name='GenericInvoice', + fields=[ + ('id', models.AutoField(verbose_name='ID', auto_created=True, primary_key=True, serialize=False)), + ('name', models.CharField(verbose_name='name', max_length=100)), + ('journal', models.ForeignKey(to='accounting.GeneralJournal', related_name='invoices')), + ], + ), + migrations.CreateModel( + name='Product', + fields=[ + ('id', models.AutoField(verbose_name='ID', auto_created=True, primary_key=True, serialize=False)), + ('name', models.CharField(verbose_name='name', max_length=30)), + ('description', models.TextField(verbose_name='description')), + ('code', models.CharField(verbose_name='code', max_length=10)), + ('purchase_price', accounting.models.CurrencyField(max_digits=12, decimal_places=2, verbose_name='purchase price')), + ('selling_price', accounting.models.CurrencyField(max_digits=12, decimal_places=2, verbose_name='selling price')), + ('special_selling_price', accounting.models.CurrencyField(max_digits=12, decimal_places=2, verbose_name='special selling price')), + ('icon', models.ImageField(null=True, upload_to='products')), + ], + ), + migrations.CreateModel( + name='ProductType', + fields=[ + ('id', models.AutoField(verbose_name='ID', auto_created=True, primary_key=True, serialize=False)), + ('name', models.CharField(verbose_name='name', max_length=30)), + ('description', models.TextField(verbose_name='description')), + ('icon', models.ImageField(null=True, upload_to='products')), + ], + ), + migrations.AddField( + model_name='product', + name='product_type', + field=models.ForeignKey(null=True, to='accounting.ProductType', related_name='products'), + ), + ] diff --git a/accounting/migrations/0002_auto_20160128_1538.py b/accounting/migrations/0002_auto_20160128_1538.py new file mode 100644 index 00000000..d379728b --- /dev/null +++ b/accounting/migrations/0002_auto_20160128_1538.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounting', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='product', + name='description', + field=models.TextField(verbose_name='description', blank=True), + ), + migrations.AlterField( + model_name='product', + name='icon', + field=models.ImageField(null=True, upload_to='products', blank=True), + ), + migrations.AlterField( + model_name='product', + name='product_type', + field=models.ForeignKey(to='accounting.ProductType', null=True, blank=True, related_name='products'), + ), + migrations.AlterField( + model_name='producttype', + name='description', + field=models.TextField(verbose_name='description', null=True, blank=True), + ), + migrations.AlterField( + model_name='producttype', + name='icon', + field=models.ImageField(null=True, upload_to='products', blank=True), + ), + ] diff --git a/accounting/migrations/__init__.py b/accounting/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/accounting/models.py b/accounting/models.py new file mode 100644 index 00000000..d6ac8222 --- /dev/null +++ b/accounting/models.py @@ -0,0 +1,94 @@ +from django.db import models +from django.utils.translation import ugettext_lazy as _ + +from decimal import Decimal +from core.models import User + +class CurrencyField(models.DecimalField): + """ + This is a custom database field used for currency + """ + __metaclass__ = models.SubfieldBase + + def __init__(self, *args, **kwargs): + kwargs['max_digits'] = 12 + kwargs['decimal_places'] = 2 + super(CurrencyField, self).__init__(*args, **kwargs) + + def to_python(self, value): + try: + return super(CurrencyField, self).to_python(value).quantize(Decimal("0.01")) + except AttributeError: + return None + +class Customer(models.Model): + """ + This class extends a user to make a customer. It adds some basic customers informations, such as the accound ID, and + is used by other accounting classes as reference to the customer, rather than using User + """ + user = models.OneToOneField(User, primary_key=True) + account_id = models.CharField(_('account id'), max_length=10, unique=True) + + class Meta: + verbose_name = _('customer') + verbose_name_plural = _('customers') + + def __str__(self): + return self.user.username + +class ProductType(models.Model): + """ + This describes a product type + Useful only for categorizing, changes are made at the product level + """ + name = models.CharField(_('name'), max_length=30) + description = models.TextField(_('description'), null=True, blank=True) + icon = models.ImageField(upload_to='products', null=True, blank=True) + + def __str__(self): + return self.name + +class Product(models.Model): + """ + This describes a product, with all its related informations + """ + name = models.CharField(_('name'), max_length=30) + description = models.TextField(_('description'), blank=True) + product_type = models.ForeignKey(ProductType, related_name='products', null=True, blank=True) + code = models.CharField(_('code'), max_length=10) + purchase_price = CurrencyField(_('purchase price')) + selling_price = CurrencyField(_('selling price')) + special_selling_price = CurrencyField(_('special selling price')) + icon = models.ImageField(upload_to='products', null=True, blank=True) + + def __str__(self): + return self.name + +class GeneralJournal(models.Model): + """ + Class storing all the invoices for a period of time + """ + start_date = models.DateField(_('start date')) + end_date = models.DateField(_('end date'), null=True, blank=True, default=None) + name = models.CharField(_('name'), max_length=30) + closed = models.BooleanField(_('is closed'), default=False) + # When clubs are done: ForeignKey(Proprietary) + + def __str__(self): + return self.name + + +class GenericInvoice(models.Model): + """ + This class is a generic invoice, made to be extended with some special cases (eg: for the internal accounting, payment + system, etc...) + """ + journal = models.ForeignKey(GeneralJournal, related_name="invoices", null=False) + name = models.CharField(_('name'), max_length=100) + + def __str__(self): + return self.journal.name+' - '+self.name + + +# TODO: CountingInvoice in Counting app extending GenericInvoice +# - ManyToMany Product diff --git a/accounting/tests.py b/accounting/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/accounting/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/accounting/views.py b/accounting/views.py new file mode 100644 index 00000000..91ea44a2 --- /dev/null +++ b/accounting/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/core/admin.py b/core/admin.py index 699e9e85..3d943daa 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,5 +1,5 @@ from django.contrib import admin -from .models import User, Page, Group +from core.models import User, Page, Group admin.site.register(User) diff --git a/core/management/commands/setup.py b/core/management/commands/setup.py index 11ef3913..e25bbdcc 100755 --- a/core/management/commands/setup.py +++ b/core/management/commands/setup.py @@ -2,7 +2,10 @@ import os from django.core.management.base import BaseCommand, CommandError from django.core.management import call_command from django.conf import settings + + from core.models import Group, User, Page, PageRev +from accounting.models import Customer, GeneralJournal, ProductType, Product class Command(BaseCommand): help = "Set up a new instance of the Sith AE" @@ -13,8 +16,9 @@ class Command(BaseCommand): def handle(self, *args, **options): try: os.unlink(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))), 'db.sqlite3')) - except: - pass + os.mkdir(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))))+'/data') + except Exception as e: + print(e) call_command('migrate') u = User(username='root', last_name="", first_name="Bibou", email="ae.info@utbm.fr", @@ -45,3 +49,11 @@ class Command(BaseCommand): Cette page vise à documenter la syntaxe *Markdown* utilisée sur le site. """).save() + # Accounting test values: + Customer(user=s, account_id="6568j").save() + p = ProductType(name="Bières bouteilles") + p.save() + Product(name="Barbar", code="BARB", product_type=p, purchase_price="1.50", selling_price="1.7", + special_selling_price="1.6").save() + GeneralJournal(start_date="2015-06-12", name="A15").save() + diff --git a/sith/settings.py b/sith/settings.py index 91d73915..3c5c99f6 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -39,6 +39,7 @@ INSTALLED_APPS = ( 'django.contrib.staticfiles', 'core', 'ae', + 'accounting', ) MIDDLEWARE_CLASSES = ( @@ -98,6 +99,9 @@ USE_L10N = True USE_TZ = True +# Medias +MEDIA_ROOT = './data/' +MEDIA_URL = '/data/' # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.8/howto/static-files/ diff --git a/sith/urls.py b/sith/urls.py index cd36ee73..546fb12e 100644 --- a/sith/urls.py +++ b/sith/urls.py @@ -15,6 +15,8 @@ Including another URLconf """ from django.conf.urls import include, url from django.contrib import admin +from django.conf.urls.static import static +from django.conf import settings handler403 = "core.views.forbidden" handler404 = "core.views.not_found" @@ -23,4 +25,4 @@ urlpatterns = [ url(r'^', include('core.urls', namespace="core", app_name="core")), url(r'^ae/', include('ae.urls', namespace="ae", app_name="ae")), url(r'^admin/', include(admin.site.urls)), -] +] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) # TODO: remove me for production!!!