新手分享边爬文边写的 Django 用 admin 开发的简易进销存系统-5

4.销货(sale)模块

4.1.models.py 设定

4.1.1.订单

订单模块是本项目最起始的模块,有了订单之后才会有接下去的所有动作,本模块一样分成单头与单身。

CAT_CHOICES = [
    ('1', 'CAT1'),
    ('2', 'CAT2'),
    ('3', 'CAT3'),
]


ORDER_CHOICES = [
    ('N', u'尚未出货'),
    ('A', u'全部出货'),
    ('P', u'部分出货'),
    ('O', u'超额出货'),
]


class Order(models.Model):
    cat_id = models.CharField(max_length=2, choices=CAT_CHOICES, blank=False, verbose_name=u'Cat.')
    pi_no = models.CharField(max_length=16, verbose_name='PI #', blank=False, null=False, unique=True)
    po_no = models.CharField(max_length=16, verbose_name='PO #', blank=True)
    customer = models.ForeignKey(Customer, verbose_name=u'客户', on_delete=models.PROTECT,
                                 limit_choices_to={'status': 'A'},)
    is_active = models.BooleanField(verbose_name=u'是否有效', default=True)
    is_urgency = models.BooleanField(verbose_name=u'是否为急单', default=False)
    status = models.CharField(max_length=1, choices=ORDER_CHOICES, default='N', verbose_name=u'状态')
    etd = models.DateField(blank=False, null=False, verbose_name=u'预定出货日期')
    created = models.DateTimeField(auto_now_add=True, verbose_name=u'订购时间')
    create_user = models.ForeignKey('auth.User', verbose_name=u'建立人员', on_delete=models.PROTECT)

    def get_absolute_url(self):
        return reverse('sale:order_detail', kwargs={'pk': self.pk})

    def __str__(self):
        return 'Id:{}-PI #:{}- PO #:{}'.format(self.id, self.pi_no, self.po_no)

    class Meta:
        verbose_name = '订单'
        verbose_name_plural = verbose_name
        permissions = (
            ('active_order', '可中止订单'),
        )

4.1.2.订单单身

class OrderProduct(models.Model):
    num = models.CharField(max_length=2, default='', verbose_name=u'单身项次')
    order = models.ForeignKey(Order, verbose_name=u'订单单头', on_delete=models.CASCADE)
    product = models.ForeignKey(Product, verbose_name=u'商品', on_delete=models.DO_NOTHING,
                                limit_choices_to={'status': 'A'},)
    quantity = models.PositiveIntegerField(blank=False, verbose_name=u'订购数量')
    price = models.DecimalField(max_digits=16, decimal_places=4, blank=False, verbose_name=u'未税金额')
    tax = models.DecimalField(max_digits=16, decimal_places=4, blank=False, verbose_name=u'税金')
    tax_price = models.DecimalField(max_digits=16, decimal_places=4, blank=False, verbose_name=u'含税金额')
    subtotal = models.DecimalField(max_digits=16, decimal_places=4, blank=False, verbose_name=u'小计未税金额')
    tax_subtotal = models.DecimalField(max_digits=16, decimal_places=4, blank=False, verbose_name=u'小计含税金额')
    currency = models.ForeignKey(Currency, verbose_name=u'币别', blank=False, on_delete=models.DO_NOTHING)
    description = models.CharField(max_length=256, verbose_name=u'描述', blank=True)

    def save(self, *args, **kwargs):
        if self.num == '':
            #项次的格式是01,02...
            order_products = OrderProduct.objects.filter(order=self.order)
            num = "{0:02d}".format(order_products.count() + 1)
            self.num = "{0:02d}".format(int(num))

        product = Product.objects.get(title=self.product)
        self.price = product.price
        self.tax = product.tax
        self.currency = product.currency
        self.tax_price = product.tax_price
        self.subtotal = product.price * self.quantity
        self.tax_subtotal = product.tax_price * self.quantity
        super().save(*args, **kwargs)

    def __str__(self):
        return '{}'.format(self.num)

    class Meta:
        verbose_name = '订单单身'
        verbose_name_plural = verbose_name

4.1.3.出货单

接到订单后,如果商品库存有的话则可以安排出给客户,如果没有的话则要使用制程单制作好商品后出货,出货单一样分单头单身。

class Ship(models.Model):
    order = models.ForeignKey(Order, verbose_name=u'对应订单', on_delete=models.PROTECT)
    customer = models.ForeignKey(Customer, verbose_name=u'客户', on_delete=models.PROTECT,
                                 limit_choices_to={'status': 'A'})
    is_active = models.BooleanField(verbose_name=u'是否有效', default=False)
    is_delay = models.BooleanField(verbose_name=u'是否延迟', default=False)
    created = models.DateTimeField(auto_now_add=True, verbose_name=u'出货时间')
    create_user = models.ForeignKey('auth.User', verbose_name=u'建立人员', on_delete=models.PROTECT)

    def get_absolute_url(self):
        return reverse('sale:ship_detail', kwargs={'pk': self.pk})

    def __str__(self):
        return '{}'.format(self.id)

    class Meta:
        verbose_name = '出货单'
        verbose_name_plural = verbose_name
        permissions = (
            ('active_ship', '可中止出货单'),
        )

4.1.4.出货单单身

class ShipDetail(models.Model):
    num = models.CharField(max_length=2, default='', verbose_name=u'单身项次')
    ship = models.ForeignKey(Ship, verbose_name=u'出货单单头', on_delete=models.CASCADE)
    product = models.ForeignKey(Product, verbose_name=u'出货商品', on_delete=models.CASCADE)
    quantity = models.PositiveIntegerField(blank=False, verbose_name=u'出货数量')
    description = models.CharField(max_length=256, verbose_name=u'描述', blank=True)

    def save(self, *args, **kwargs):
        if self.num == '':
            #项次的格式是01,02...
            ship_detail = ShipDetail.objects.filter(ship=self.ship)
            num = "{0:02d}".format(ship_detail.count() + 1)
            self.num = "{0:02d}".format(int(num))
        super().save(*args, **kwargs)

    def __str__(self):
        return '{}'.format(self.num)

    class Meta:
        verbose_name = '出货单单身'
        verbose_name_plural = verbose_name

4.2.admin.py 设定

本模块的admin设定也相对简单,所以也不多说明了。

from django import forms
from django.contrib import admin, messages
from django.contrib.auth import get_permission_codename

import datetime
from .models import Order, OrderProduct, Ship, ShipDetail
from finance.models import Receivable, ReceivableDetail
from inventory.models import Ptran


"""
订单单身检查
1.最少要有一个单身
2.商品不可重复
3.商品数量是否大于0
"""
class OrderProductCheckInlineFormset(forms.models.BaseInlineFormSet):
    def clean(self):
        count = 0
        product_list = []
        for form in self.forms:
            if form.cleaned_data:
                count += 1

                quantity = form.cleaned_data.get('quantity')
                if quantity <= 0:
                    raise forms.ValidationError(u'订单中商品数量不得为0或是负数。')

                product_id = form.cleaned_data.get('product')
                if product_id in product_list:
                    raise forms.ValidationError(u'订单中商品不得重复。')
                else:
                    product_list.append(product_id)

        if count < 1:
            raise forms.ValidationError(u'您最少必须输入一笔订单单身。')


class OrderProductInline(admin.TabularInline):
    formset = OrderProductCheckInlineFormset
    model = OrderProduct
    fields = ['product', 'quantity', 'description']
    raw_id_fields = ['product']
    extra = 0


class OrderAdmin(admin.ModelAdmin):
    list_display = ['id', 'cat_id', 'pi_no', 'po_no', 'customer', 'is_urgency', 'status',
                    'etd', 'created', 'create_user']
    fields = ['cat_id', 'po_no', 'customer', 'is_urgency', 'etd']
    list_filter = ['is_urgency', 'status']
    actions = ['make_actived']
    inlines = [OrderProductInline]
    view_on_site = False
    list_per_page = 10
    list_max_show_all = 100
    date_hierarchy = 'created'

    def save_model(self, request, obj, form, change):
        if not change:
            obj.create_user = request.user

            """
            pi_no的格式是R201901121
            R:开头
            201901:2019年1月
            121:三码流水码
            """
            pattern = 'R' + datetime.datetime.now().strftime("%Y%m")
            orders = Order.objects.filter(pi_no__startswith=pattern)
            obj.pi_no = pattern + "{0:03d}".format(orders.count() + 1)
        super().save_model(request, obj, form, change)

    def make_actived(self, request, queryset):
        rows = queryset.update(is_active=False)
        if rows > 0:
            self.message_user(request, u'已完成终止订单动作')
    make_actived.allowed_permissions = ('active',)
    make_actived.short_description = u'终止订单'

    def has_active_permission(self, request):
        opts = self.opts
        codename = get_permission_codename('active', opts)
        return request.user.has_perm('%s.%s' % (opts.app_label, codename))


admin.site.register(Order, OrderAdmin)


"""
出货单单身检查
1.最少要有一个单身
2.商品库存是否足够
3.出货数量是否至少一笔大于0
4.出货数量不得是负数
4.单身商品不得重复
"""
class ShipDetailCheckInlineFormset(forms.models.BaseInlineFormSet):
    def clean(self):
        detail_count = 0
        detail_amount = 0

        product_list = []
        for form in self.forms:
            if form.cleaned_data:
                detail_count += 1

                quantity = form.cleaned_data.get('quantity')
                if quantity < 0:
                    raise forms.ValidationError(u'出货单中商品数量不得是负数。')
                elif quantity > 0:
                    detail_amount += 1

                product = form.cleaned_data.get('product')
                quantity = int(form.cleaned_data.get('quantity'))
                if product.stock < quantity:
                    raise forms.ValidationError(u"商品[{}-{}]库存 {},无法满足此次出货量 {},"
                                                u"请先填写制程单增加商品库存。".format(product.id, product.title,
                                                                         product.stock, quantity))

                if product.id in product_list:
                    raise forms.ValidationError(u"单身商品[{}-{}]已重复,"
                                                u"请重新填写出货单。".format(product.id, product.title))
                else:
                    product_list.append(product.id)
        if detail_count < 1:
            raise forms.ValidationError(u'您必须最少输入一笔订单单身')
        if detail_amount < 1:
            raise forms.ValidationError(u'您必须最少一笔出货单单身数量大于0')


class ShipDetailInline(admin.TabularInline):
    formset = ShipDetailCheckInlineFormset
    model = ShipDetail
    fields = ['product', 'quantity', 'description']
    raw_id_fields = ['product']
    extra = 0


"""
出货时的相关动作如下:
1.对于每个出货单单身而言
   i.新增 商品库存异动(Ptran)
  ii.如果出货数量>0时则新增 应收帐款单身(ReceivableDetail)
 iii.减少 商品库存量(product.stock)
2.对于整个出货单而言
   i.将出货单的 状态修改为有效出货(is_active=True) 检查出货日期是否有延迟(is_delay)
  ii.如果出货数量>0时则新增一笔 应收帐款(Receivable)
 iii.检查订单数量是否满足,决定订单"状态",('A', '全部出货'),('P', '部分出货')
"""
class ShipAdmin(admin.ModelAdmin):
    list_display = ['id', 'order', 'customer', 'is_active', 'is_delay', 'created', 'create_user']
    fields = ['order']
    actions = ['make_actived']
    inlines = [ShipDetailInline]
    view_on_site = False
    list_per_page = 10
    list_max_show_all = 100
    date_hierarchy = 'created'

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        if db_field.name == 'order':
            kwargs['queryset'] = Order.objects.filter(is_active=True).exclude(status='A')
        return super(ShipAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)

    def save_model(self, request, obj, form, change):
        if not change:
            obj.create_user = request.user
            order = obj.order
            obj.customer = order.customer
        super().save_model(request, obj, form, change)

    def save_related(self, request, form, formsets, change):
        super().save_related(request, form, formsets, change)

        ship = form.save(commit=False)

        #新增应收帐款单头
        receivable = Receivable()
        receivable.ship = ship
        receivable.customer = ship.customer
        #应收日期=出货日期 + 帐期
        receivable.receivabled = datetime.datetime.now().date() + datetime.timedelta(days=ship.customer.period.period)
        receivable.create_user = ship.create_user
        receivable.save()

        ship_details = ShipDetail.objects.filter(ship=ship)
        for ship_detail in ship_details:
            #新增商品库存异动
            product = ship_detail.product
            stock = product.stock
            tran_quantity = ship_detail.quantity

            ptran = Ptran()
            ptran.product = product
            ptran.source_form = 'SHIP'
            ptran.source_id = ship.id
            ptran.from_quantity = stock
            ptran.tran_quantity = 0 - tran_quantity
            ptran.to_quantity = stock - tran_quantity
            ptran.create_user = request.user
            ptran.save()

            #新增应收帐款单身
            receivable_detail = ReceivableDetail()
            receivable_detail.receivable = receivable
            receivable_detail.product = product
            receivable_detail.amount = product.tax_price * tran_quantity
            receivable_detail.currency = product.currency
            receivable_detail.save()

            #减少商品库存量
            product.stock = stock - tran_quantity
            product.save()

        order = form.cleaned_data.get('order')
        #修改出货单状态
        ship.is_active = True
        today = datetime.datetime.now().date()
        if today > order.etd:
            ship.is_delay = True
        ship.save()

        #决定对应订单状态('A', '全部出货'),('P', '部分出货'),('O', '超额出货')如果有一个订单单身数量没有全部出完的话,则状态为P
        order_products = OrderProduct.objects.filter(order=order)
        ships = Ship.objects.filter(order=order)
        status = 'A'
        for order_product in order_products:
            product = order_product.product
            shipped_quantity = 0
            for ship in ships.all():
                ship_details = ShipDetail.objects.filter(product=product, ship=ship)
                if ship_details.all().count() > 0:
                    for ship_detail in ship_details.all():
                        shipped_quantity += ship_detail.quantity

            if shipped_quantity < order_product.quantity:
                status = 'P'
            else:
                if shipped_quantity > order_product.quantity:
                    #如果有商品是部分出货P,就算是有超额出货也还是算成P
                    if status is 'P':
                        status = 'P'
                    else:
                        status = 'O'

        order.status = status
        order.save()

        #如果没有发生错误则修改应收帐款状态
        receivable.is_active = True
        receivable.save()

    def make_actived(self, request, queryset):
        rows = queryset.update(is_active=False)
        if rows > 0:
            self.message_user(request, u'已完成终止出货单动作')
    make_actived.allowed_permissions = ('active',)
    make_actived.short_description = u'终止出货单'

    def has_active_permission(self, request):
        opts = self.opts
        codename = get_permission_codename('active', opts)
        return request.user.has_perm('%s.%s' % (opts.app_label, codename))


admin.site.register(Ship, ShipAdmin)

4.3.templates 设定

销货模块里面只有订单出货单两个主体,出货单我希望的呈现方式如下:
1.先让使用者选择对应的订单
在这里插入图片描述
2.选择好对应订单后再显示其他字段,并在单身中带出订单单身
在这里插入图片描述
使用的方式一样是jquery(change_form.html)与views.py,位置与代码如下:
在这里插入图片描述

{% extends "admin/base_site.html" %}
{% load i18n admin_urls static admin_modify %}

{% block extrahead %}{{ block.super }}
<script type="text/javascript" src="{% url 'admin:jsi18n' %}"></script>
{{ media }}
{% endblock %}

{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}">{% endblock %}

{% block coltype %}colM{% endblock %}

{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} change-form{% endblock %}

{% if not is_popup %}
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
&rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a>
&rsaquo; {% if has_view_permission %}<a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>{% else %}{{ opts.verbose_name_plural|capfirst }}{% endif %}
&rsaquo; {% if add %}{% blocktrans with name=opts.verbose_name %}Add {{ name }}{% endblocktrans %}{% else %}{{ original|truncatewords:"18" }}{% endif %}
</div>
{% endblock %}
{% endif %}

{% block content %}<div id="content-main">
{% block object-tools %}
{% if change %}{% if not is_popup %}
  <ul class="object-tools">
    {% block object-tools-items %}
      {% change_form_object_tools %}
    {% endblock %}
  </ul>
{% endif %}{% endif %}
{% endblock %}
<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post" id="{{ opts.model_name }}_form" novalidate>{% csrf_token %}{% block form_top %}{% endblock %}
<div>
{% if is_popup %}<input type="hidden" name="{{ is_popup_var }}" value="1">{% endif %}
{% if to_field %}<input type="hidden" name="{{ to_field_var }}" value="{{ to_field }}">{% endif %}
{% if save_on_top %}{% block submit_buttons_top %}{% submit_row %}{% endblock %}{% endif %}
{% if errors %}
    <p class="errornote">
    {% if errors|length == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %}
    </p>
    {{ adminform.form.non_field_errors }}
{% endif %}

{% block field_sets %}
{% for fieldset in adminform %}
  {% include "./includes/fieldset.html" %}
{% endfor %}
{% endblock %}

{% block after_field_sets %}{% endblock %}

{% block inline_field_sets %}
{% for inline_admin_formset in inline_admin_formsets %}
    {% include inline_admin_formset.opts.template %}
{% endfor %}
{% endblock %}

{% block after_related_objects %}{% endblock %}

{% block submit_buttons_bottom %}{% submit_row %}{% endblock %}

{% block admin_change_form_document_ready %}
    <script type="text/javascript"
            id="django-admin-form-add-constants"
            src="{% static 'admin/js/change_form.js' %}"
            {% if adminform and add %}
                data-model-name="{{ opts.model_name }}"
            {% endif %}>
    </script>
{% endblock %}

{# JavaScript for prepopulated fields #}
{% prepopulated_fields_js %}

<script>
(function($) {
    $(document).ready(function(){
        var $form = $('#ship_form');
        var formset_name = 'shipdetail_set';
        var $formset_div = $('#' + formset_name + '-group');
        var $formset_table = $formset_div.find('table');
        var $formset_add_row_tr = $formset_div.find('tr.add-row')
        var $submit_row_div = $form.find('div.submit-row');
        var $formset_empty_row = $formset_table.find('tbody tr#' + formset_name + '-empty');
        var $formset_total_input = $formset_div.find('input#id_' + formset_name + '-TOTAL_FORMS')

        $formset_add_row_tr.hide();
        $submit_row_div.hide();

        var href = location.href;
        href_list = href.split('/');
        <!-- 表示为change -->
        if (href_list[href_list.length - 2] == 'change'){
            $('div.submit-row').html('<a href="/admin/sale/ship/" class="closelink">Close</a>');
            $('div.submit-row').show();
        }

        if($('#id_order').val() == '')
        {
            <!-- 把非"对应订单"的输入字段隐藏起来 -->
            $form.find('fieldset:first>div').each(function(){
                if(!$(this).hasClass('field-order')){
                    $(this).hide();
                }
            })
            <!-- 隐藏"formset",""储存窗体按钮"" -->
            $formset_div.hide();
            $submit_row_div.hide();

            <!-- 选择"对应订单"之后 -->
            $('#id_order').change(function(){
                var order_id = $(this).val();
                if(order_id != ''){
                    $(this).attr("disabled","disabled");

                    <!-- 移除已有的出货单身 -->
                    <!-- $formset_div.find('table tbody .form-row').remove(); -->

                    <!-- 根据对应订单产生出货单身 -->
                    $.get('{% url 'sale:order_ajax_product_list' %}', {'id':order_id}, function(data){
                        if(data['code'] == 0){
                            $form.find('fieldset:first>div').each(function(){
                                if(!$(this).hasClass('field-order')){
                                    $(this).show();
                                }
                            })
                            $formset_div.show();
                            $submit_row_div.show();

                            $submit_row_div.find('input').each(function(){
                                $(this).click(function(){
                                    $('#id_order').removeAttr("disabled");
                                });
                            });

                            <!-- 将数据填入窗体中 -->
                            $tbody = $formset_table.find('tbody');
                            var products = data['products'];
                            var index = 0;
                            for(p in products){
                                total = $formset_total_input.val();
                                $new_row = $formset_empty_row.clone(true);
                                $formset_empty_row.removeClass('row' + (index + 1) % 3);
                                $formset_empty_row.addClass('row' + (index + 2) % 3);
                                $new_row.attr('id', formset_name + '-' + total);
                                $new_row.removeClass('empty-form');
                                $new_row.find('input').each(function(){
                                    name = $(this).attr('name');
                                    id = $(this).attr('id');
                                    $(this).attr('name', name.replace(/__prefix__/, total));
                                    $(this).attr('id', id.replace(/__prefix__/, total));
                                    name_list = name.split('-');
                                    if(name_list[name_list.length -1] == "product"){
                                        $(this).val(products[p]['product']['id']);
                                        $(this).attr('readonly', 'readonly');
                                        $(this).parent().append(products[p]['product']['title']);
                                    }
                                    else if(name_list[name_list.length -1] == "quantity"){
                                        $(this).val(products[p]['quantity']);
                                    }
                                });
                                $new_row.find('a').remove();

                                $formset_empty_row.before($new_row);

                                total++;
                                $formset_total_input.val(total);

                                index++;
                            }

                            if (data['msg'] != '')
                                alert(data['msg']);
                        }
                        else{
                            alert(data['msg']);
                        }
                    }, 'json')
                }
            });
        }
        else{
            $('#id_order').attr("disabled","disabled");
        }
    });
})(django.jQuery);
</script>
</div>
</form></div>
{% endblock %}

4.4.urls.py 设定

from django.urls import path
from django.contrib.auth.decorators import login_required
from . import views

app_name = 'sale'

urlpatterns = [
    path('order/ajax/list/', views.order_ajax_product_list, name='order_ajax_product_list'),
]

4.5.views.py 设定

from django.http import HttpResponse, JsonResponse, HttpResponseRedirect
from .models import Order, OrderProduct, Ship, ShipDetail


def order_ajax_product_list(request):
    return_dict = {}
    #判断用户是否有权限检视订单
    if request.user.has_perm('sale.view_order'):
        order_id = request.GET.get('id')
        order = Order.objects.get(id=order_id)
        return_dict['code'] = 0
        return_dict['msg'] = ''
        return_dict['products'] = []
        order_products = OrderProduct.objects.filter(order=order)

        for p in order_products.all():
            product = p.product
            quantity = p.quantity

            # 如果该订单有其他对应出货单,则需扣掉已出货的商品数量
            ships = Ship.objects.filter(order=order, is_active=True)
            if ships.count() > 0:
                return_dict['msg'] = u'此订单已有对应出货单,出货单单号如下:'
                for s in ships.all():
                    return_dict['msg'] += ' ' + str(s.id)
                    ship_product = ShipDetail.objects.filter(ship=s, product=product)
                    if ship_product.all().count() > 0:
                        quantity -= ship_product[0].quantity

            product_info = {}
            product_info['id'] = product.id
            product_info['title'] = product.title
            product_dict = {'product': product_info, 'quantity': quantity}
            return_dict['products'].append(product_dict)
    else:
        return_dict['code'] = 1
        return_dict['msg'] = u"您无权限浏览订单"
    return JsonResponse(return_dict)

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值