浏览代码

生产佳 初始化

wushaodong 3 年之前
当前提交
f0b211d2a2
共有 100 个文件被更改,包括 15894 次插入0 次删除
  1. 28 0
      .gitignore
  2. 0 0
      apps/__init__.py
  3. 1 0
      apps/account/__init__.py
  4. 83 0
      apps/account/decorators.py
  5. 12 0
      apps/account/factories.py
  6. 0 0
      apps/account/migrations/__init__.py
  7. 211 0
      apps/account/models.py
  8. 101 0
      apps/account/serializers.py
  9. 39 0
      apps/account/tests.py
  10. 65 0
      apps/account/tokens.py
  11. 33 0
      apps/account/urls.py
  12. 480 0
      apps/account/views.py
  13. 400 0
      apps/base.py
  14. 0 0
      apps/config/__init__.py
  15. 12 0
      apps/config/filters.py
  16. 0 0
      apps/config/migrations/__init__.py
  17. 37 0
      apps/config/models.py
  18. 10 0
      apps/config/serializers.py
  19. 9 0
      apps/config/urls.py
  20. 46 0
      apps/config/views.py
  21. 0 0
      apps/customer/__init__.py
  22. 13 0
      apps/customer/filters.py
  23. 0 0
      apps/customer/migrations/__init__.py
  24. 44 0
      apps/customer/models.py
  25. 44 0
      apps/customer/resources.py
  26. 57 0
      apps/customer/serializers.py
  27. 15 0
      apps/customer/urls.py
  28. 134 0
      apps/customer/views.py
  29. 0 0
      apps/dashboard/__init__.py
  30. 二进制
      apps/dashboard/forms.pyd
  31. 0 0
      apps/dashboard/migrations/__init__.py
  32. 5 0
      apps/dashboard/models.py
  33. 3 0
      apps/dashboard/tests.py
  34. 10 0
      apps/dashboard/urls.py
  35. 57 0
      apps/dashboard/views.py
  36. 52 0
      apps/exceptions.py
  37. 0 0
      apps/foundation/__init__.py
  38. 89 0
      apps/foundation/consts.py
  39. 11 0
      apps/foundation/filters.py
  40. 16 0
      apps/foundation/forms.py
  41. 16 0
      apps/foundation/managers.py
  42. 0 0
      apps/foundation/migrations/__init__.py
  43. 17 0
      apps/foundation/mixins.py
  44. 121 0
      apps/foundation/models.py
  45. 22 0
      apps/foundation/resources.py
  46. 67 0
      apps/foundation/serializers.py
  47. 17 0
      apps/foundation/urls.py
  48. 107 0
      apps/foundation/views.py
  49. 0 0
      apps/goods/__init__.py
  50. 55 0
      apps/goods/filters.py
  51. 0 0
      apps/goods/migrations/__init__.py
  52. 157 0
      apps/goods/models.py
  53. 174 0
      apps/goods/resources.py
  54. 188 0
      apps/goods/serializers.py
  55. 34 0
      apps/goods/urls.py
  56. 1059 0
      apps/goods/views.py
  57. 0 0
      apps/material/__init__.py
  58. 75 0
      apps/material/filters.py
  59. 0 0
      apps/material/migrations/__init__.py
  60. 405 0
      apps/material/models.py
  61. 281 0
      apps/material/resources.py
  62. 465 0
      apps/material/serializers.py
  63. 58 0
      apps/material/urls.py
  64. 1573 0
      apps/material/views.py
  65. 0 0
      apps/office/__init__.py
  66. 18 0
      apps/office/filters.py
  67. 0 0
      apps/office/migrations/__init__.py
  68. 114 0
      apps/office/models.py
  69. 177 0
      apps/office/serializers.py
  70. 19 0
      apps/office/urls.py
  71. 303 0
      apps/office/views.py
  72. 0 0
      apps/order/__init__.py
  73. 46 0
      apps/order/filters.py
  74. 0 0
      apps/order/migrations/__init__.py
  75. 309 0
      apps/order/models.py
  76. 192 0
      apps/order/resources.py
  77. 328 0
      apps/order/serializers.py
  78. 37 0
      apps/order/urls.py
  79. 968 0
      apps/order/views.py
  80. 0 0
      apps/plan/__init__.py
  81. 36 0
      apps/plan/filters.py
  82. 0 0
      apps/plan/migrations/__init__.py
  83. 200 0
      apps/plan/models.py
  84. 95 0
      apps/plan/resources.py
  85. 255 0
      apps/plan/serializers.py
  86. 24 0
      apps/plan/urls.py
  87. 518 0
      apps/plan/views.py
  88. 0 0
      apps/product/__init__.py
  89. 7 0
      apps/product/manager.py
  90. 0 0
      apps/product/migrations/__init__.py
  91. 77 0
      apps/product/models.py
  92. 52 0
      apps/product/serializers.py
  93. 0 0
      apps/purchase/__init__.py
  94. 153 0
      apps/purchase/filters.py
  95. 0 0
      apps/purchase/migrations/__init__.py
  96. 862 0
      apps/purchase/models.py
  97. 419 0
      apps/purchase/resources.py
  98. 744 0
      apps/purchase/serializers.py
  99. 88 0
      apps/purchase/urls.py
  100. 2845 0
      apps/purchase/views.py

+ 28 - 0
.gitignore

@@ -0,0 +1,28 @@
+*.pyc
+*.sh
+*.bat
+00*.py
+build*.*
+autodelopy*.*
+venv
+run.bat
+tmp/*
+uploads/*
+logs/*
+build/*
+.idea/workspace.xml
+.idea/misc.xml
+.idea/pfwin.xml
+.DS_Store
+wsd
+run.bat
+.idea
+pfwin/local_settings.*
+pfwin/touch_settings.*
+
+
+apps/dashboard/forms.py
+libs/printhelper.py
+
+
+

+ 0 - 0
apps/__init__.py


+ 1 - 0
apps/account/__init__.py

@@ -0,0 +1 @@
+#coding=utf-8

+ 83 - 0
apps/account/decorators.py

@@ -0,0 +1,83 @@
+#coding=utf-8
+
+from django.views.decorators.csrf import csrf_exempt
+from django.utils.functional import wraps
+
+from libs.http import ForbiddenJSONResponse
+from apps.exceptions import CustomError
+from tokens import token_generator
+from models import User
+
+def token_required(view_func):
+    """Decorator which ensures the user has provided a correct user and token pair."""
+
+    @csrf_exempt
+    @wraps(view_func)
+    def _wrapped_view(request, *args, **kwargs):
+        user_id = request.META.get('HTTP_USER_ID')
+        token = request.META.get('HTTP_ACCESS_TOKEN')
+
+        if user_id and token:
+            try:
+                user = User.objects.get(pk=user_id)
+            except:
+                return ForbiddenJSONResponse()
+
+            valid = token_generator.check_token(user, token)
+            if valid:
+                request.user = user
+                return view_func(request, *args, **kwargs)
+
+        return ForbiddenJSONResponse()
+    return _wrapped_view
+
+decorator_with_arguments = lambda decorator: lambda *args, **kwargs: lambda func: decorator(func, *args, **kwargs)
+
+@decorator_with_arguments
+def permission_required(function, perm):
+    def _function(request, *args, **kwargs):
+        user_id = request.META.get('HTTP_USER_ID')
+        token = request.META.get('HTTP_ACCESS_TOKEN')
+        if user_id and token:
+            try:
+                user = User.objects.get(pk=user_id)
+            except:
+                return ForbiddenJSONResponse()
+
+            valid = token_generator.check_token(user, token)
+            if valid:
+                request.user = user
+        else:
+            return ForbiddenJSONResponse()
+
+        if request.user.has_perm(perm):
+            return function(request, *args, **kwargs)
+        else:
+            from django.contrib.auth.models import Permission
+            from libs.http import JSONError
+            ps = perm.split('.')
+            try:
+                p = Permission.objects.get(codename=ps[1], content_type__app_label=ps[0])
+            except:
+                return JSONError(u"权限配置错误!")
+
+            return JSONError(u"您没有[%s-%s]权限,无法执行该操作,请联系管理员分配权限!" % (p.content_type.name, p.name))
+    return _function
+
+def valid_permission(user,perm):
+    if user.has_perm(perm):
+        return
+
+    from django.contrib.auth.models import Permission
+    ps = perm.split('.')
+    try:
+        p = Permission.objects.get(codename=ps[1], content_type__app_label=ps[0])
+    except:
+        raise CustomError(u"权限配置错误!")
+    raise CustomError(u"您没有[%s-%s]权限,无法执行该操作,请联系管理员分配权限!" % (p.content_type.name, p.name))
+
+
+def isHasPermissions(user, perm):
+    if user.has_perm(perm):
+        return True
+    return False

+ 12 - 0
apps/account/factories.py

@@ -0,0 +1,12 @@
+#coding=utf-8
+
+import factory
+from django.conf import settings
+
+from models import User
+
+class UserFactory(factory.django.DjangoModelFactory):
+    class Meta:
+        model = User
+
+    gender = settings.MALE

+ 0 - 0
apps/account/migrations/__init__.py


+ 211 - 0
apps/account/models.py

@@ -0,0 +1,211 @@
+#coding=utf-8
+
+from django.db import models
+from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, Group, PermissionsMixin
+from django.utils import timezone
+from django.conf import settings
+
+from apps.exceptions import CustomError
+from apps.foundation.consts import CONTENT_TYPE_SORTING, MENU_TO_MODEL
+
+from apps.foundation.models import BizLog
+
+class UserManager(BaseUserManager):
+    def sort_perms(self, perms):
+        def get_index(app_label, model):
+            try:
+                return CONTENT_TYPE_SORTING.index('{}-{}'.format(app_label, model))
+            except:
+                return 9999
+
+        perms = perms.order_by('content_type__model', 'id')
+        perms = sorted(perms, key=lambda n: get_index(n.content_type.app_label, n.content_type.model))
+        return perms
+
+    def get_menuname_of_contenttype(self, app_label, model):
+        for menu in MENU_TO_MODEL:
+            val = '{}-{}'.format(app_label, model)
+            if val in menu[1]:
+                return menu[0]
+        return u'未分类'
+
+    def save_group(self, id, name, permissions, user):
+        name = name.strip(u' ')
+        #old_permissions = None
+        if id == None or id == '':
+            is_exist = Group.objects.filter(name=name).first()
+            if is_exist:
+                raise CustomError(u'名称为[%s]的权限组已存在' % name)
+            group = Group.objects.create(name=name)
+            BizLog.objects.addnew(user, BizLog.INSERT, u"添加权限组[%s],id=%d" % (group.name, group.id))
+        else:
+            is_exist = Group.objects.filter(name=name).exclude(pk=id).first()
+            if is_exist:
+                raise CustomError(u'名称为[%s]的权限组已存在' % name)
+            group = Group.objects.filter(pk=id).first()
+            if not group:
+                raise CustomError(u'未找到相应的权限组')
+            group.name = name
+            group.save()
+        #    old_permissions = [p.id for p in group.permissions.all()]
+            BizLog.objects.addnew(user, BizLog.UPDATE, u"修改权限组[%s],id=%d" % (group.name, group.id))
+        group.permissions = permissions
+
+        # 去掉下属创建权限组中的权限
+        #if old_permissions:
+        #    new_permissions = [p.id for p in group.permissions.all()]
+        #    diff = list(set(old_permissions).difference(set(new_permissions)))
+
+        #    users = User.objects.filter(groups=group)
+        #    groups = Group.objects.filter(create_user__in=users,permissions__id__in=diff)
+        #    for g in groups:
+        #        for pk in diff:
+        #            g.permissions.remove(pk)
+
+class User(AbstractBaseUser, PermissionsMixin):
+    DIMISSION = 0
+    INSERVICE = 1
+    STATUS_CHOICES = (
+        (DIMISSION, u'离职'),
+        (INSERVICE, u'在职'),
+    )
+    name = models.CharField(max_length=20, verbose_name=u"姓名")
+    username = models.CharField(max_length=30, verbose_name=u'账号', unique=True, db_index=True,error_messages={'unique': u'已存在'})
+    status = models.PositiveSmallIntegerField(choices=STATUS_CHOICES, verbose_name=u"是否在职", default=INSERVICE)
+    tel = models.CharField(max_length=15, verbose_name=u"手机号码", null=True, blank=True)
+    gender = models.PositiveSmallIntegerField(choices=settings.GENDER_CHOICES, verbose_name=u"性别",null=True,blank=True)
+    ID_card = models.CharField(max_length=18, verbose_name=u"身份证号", null=True, blank=True)
+    address = models.CharField(max_length=500, verbose_name=u"家庭住址", null=True, blank=True)
+    department = models.ForeignKey('Department', verbose_name=u"所属部门", null=True, blank=True, on_delete=models.PROTECT)
+    title = models.CharField(max_length=20, verbose_name=u"工作岗位", null=True, blank=True)
+    date_joined = models.DateTimeField(verbose_name=u'注册时间', default=timezone.now, null=True)
+
+    objects = UserManager()
+
+    USERNAME_FIELD = 'username'
+    REQUIRED_FIELDS = []
+
+    @staticmethod
+    def getById(id):
+        try:
+            id = int(id)
+        except:
+            raise CustomError(u'无效的员工ID')
+
+        instance = User.objects.filter(pk=id).first()
+        if not instance:
+            raise CustomError(u'未找到相应的员工')
+        return instance
+
+    def removeSubs(self):
+        SubDepartment.objects.filter(user=self).delete()
+        SubEmployee.objects.filter(user=self).delete()
+
+    def __str__(self):
+        return self.name
+
+    def __unicode__(self):
+        return self.name
+
+    def getSubDepartmentIds(self):
+        ids = []
+        sub_ids = SubDepartment.objects.filter(user=self).values_list('department_id', flat=True)
+        departments = Department.objects.filter(id__in=sub_ids)
+
+        for dept in departments:
+            rows = Department.objects.filter(lft__gte=dept.lft, rgt__lte=dept.rgt)
+            for row in rows:
+                if row.id not in ids:
+                    ids.append(row.id)
+        return ids
+
+    def getSubEmployeeIds(self):
+        return SubEmployee.objects.filter(user=self).values_list('employee_id', flat=True)
+
+    class Meta:
+        db_table = "auth_user"
+        verbose_name = u"人员管理"
+        ordering = ('-id',)
+        default_permissions = ()
+        permissions = (
+            ("view_user", u"浏览"),
+            ("add_user", u"添加"),
+            ("delete_user", u"删除"),
+        )
+
+class SubDepartment(models.Model):
+    user = models.ForeignKey(User, verbose_name=u"用户", on_delete=models.PROTECT)
+    department = models.ForeignKey('Department', verbose_name=u"管辖部门", on_delete=models.PROTECT)
+
+    class Meta:
+        db_table = "sub_department"
+        verbose_name = u"退货查询"
+        default_permissions = ()
+        permissions = ( # 管辖部门
+            ("view_material_godownentry_return_query", u"浏览"),
+            ("export_material_godownentry_return_query", u"导出"),
+            ("print_material_godownentry_return_query", u"打印"),
+        )
+
+
+class SubEmployee(models.Model):
+    user = models.ForeignKey(User, verbose_name=u"用户", related_name='sub_employee_ref_user', on_delete=models.PROTECT)
+    employee = models.ForeignKey(User, verbose_name=u"员工", related_name='sub_employee_ref_employee', on_delete=models.PROTECT)
+
+    class Meta:
+        db_table = "sub_employee"
+        verbose_name = u"退货查询"
+        default_permissions = ()
+        permissions = (  # 管辖员工
+            ("view_consumable_godownentry_return_query", u"浏览"),
+            ("export_consumable_godownentry_return_query", u"导出"),
+            ("print_consumable_godownentry_return_query", u"打印"),
+        )
+
+class Department(models.Model):
+    name = models.CharField(max_length=100, verbose_name=u"名称")
+    notes = models.CharField(max_length=500, verbose_name=u"备注",blank=True,null=True)
+    parent_id = models.IntegerField(verbose_name=u"父部门",null=True,blank=True)
+    lft = models.IntegerField(verbose_name=u"左值")
+    rgt = models.IntegerField(verbose_name=u"右值")
+
+    def __str__(self):
+        return self.name
+
+    def __unicode__(self):
+        return self.name
+
+    @staticmethod
+    def getById(id):
+        try:
+            id = int(id)
+        except:
+            raise CustomError(u'无效的部门ID')
+
+        instance = Department.objects.filter(pk=id).first()
+        if not instance:
+            raise CustomError(u'未找到相应的部门')
+        return instance
+
+    def getCompany(self):
+        instance = Department.objects.filter(parent_id__isnull=True, lft__lte=self.lft, rgt__gte=self.rgt).first()
+        if not instance:
+            raise CustomError(u'未找到部门所属的公司')
+        return instance
+
+    @staticmethod
+    def getLft(instance):
+        if instance:
+            return instance.lft
+        return 1
+
+    class Meta:
+        db_table = "department"
+        verbose_name = u"组织结构管理"
+        ordering = ["parent_id", 'id']
+        default_permissions = ()
+        permissions = (
+            ("view_department", u"浏览"),
+            ("add_department", u"添加"),
+            ("delete_department", u"删除"),
+        )

+ 101 - 0
apps/account/serializers.py

@@ -0,0 +1,101 @@
+#coding=utf-8
+
+from rest_framework import serializers
+from apps.exceptions import CustomError
+from django.db.models import Q
+
+from apps.foundation.models import BizLog
+from models import User, Group,SubDepartment,SubEmployee
+from apps.serializer_errors import dump_serializer_errors
+from apps.foundation.models import BizLog
+
+class EmployeeSerializer(serializers.ModelSerializer):
+    gender_text = serializers.CharField(source='get_gender_display', read_only=True)
+    status_text = serializers.CharField(source='get_status_display',read_only=True)
+
+    class Meta:
+        model = User
+        fields = '__all__'
+
+    @staticmethod
+    def factory(user, data, id=None):
+        if id:
+            instance = User.getById(id)
+        else:
+            instance = None
+        serializer = EmployeeSerializer(instance, data=data)
+        serializer.user = user
+        return serializer
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+    def validate(self, data):
+        if 'status' not in data:
+            data['status'] = User.DIMISSION
+        return data
+
+    def create(self, validated_data):
+        groups = validated_data.pop('groups')
+
+        instance = super(EmployeeSerializer, self).create(validated_data)
+        instance.set_password(validated_data['password'])
+        instance.save()
+
+        for group in groups:
+            instance.groups.add(group)
+
+        BizLog.objects.addnew(self.user, BizLog.INSERT, u"添加员工[%s],id=%s" % (instance.name, instance.id),validated_data)
+        return instance
+
+    def update(self, instance, validated_data):
+        instance.groups.clear()
+        groups = validated_data.pop('groups')
+        for group in groups:
+            instance.groups.add(group)
+
+        BizLog.objects.addnew(self.user, BizLog.UPDATE, u"修改员工[%s],id=%s" % (instance.name, instance.id),validated_data)
+        instance = super(EmployeeSerializer, self).update(instance, validated_data)
+        return instance
+
+class EmployeeSafeSerializer(serializers.ModelSerializer):
+    gender_text = serializers.CharField(source='get_gender_display', read_only=True)
+    status_text = serializers.CharField(source='get_status_display',read_only=True)
+    department_text = serializers.CharField(source='department.name', read_only=True)
+    groups = serializers.StringRelatedField(many=True, read_only=True)
+    group_ids = serializers.PrimaryKeyRelatedField(source='groups', many=True, read_only=True)
+    sub_department_ids = serializers.SerializerMethodField()
+    sub_employee_ids = serializers.SerializerMethodField()
+
+    class Meta:
+        model = User
+        exclude = ('password', )
+
+    def get_sub_department_ids(self, obj):
+        data = []
+        rows = SubDepartment.objects.filter(user=obj)
+        data.extend([s.department_id for s in rows])
+        return data
+
+    def get_sub_employee_ids(self, obj):
+        data = []
+        rows = SubEmployee.objects.filter(user=obj)
+        data.extend([s.employee_id for s in rows])
+        return data
+
+class GroupSerializer(serializers.ModelSerializer):
+    employees = serializers.StringRelatedField(source='user_set', many=True, read_only=True)
+
+    class Meta:
+        model = Group
+        fields = '__all__'
+
+class GroupComboboxSerializer(serializers.ModelSerializer):
+    value = serializers.CharField(source='id', read_only=True)
+
+    class Meta:
+        model = Group
+        fields = ('value', 'name', )

+ 39 - 0
apps/account/tests.py

@@ -0,0 +1,39 @@
+#coding=utf-8
+
+import json
+
+from django.test import TestCase
+from django.core.urlresolvers import reverse
+from django.conf import settings
+
+from models import User
+
+class AccountTests(TestCase):
+    username = 'firefish'
+    password = '1111'
+
+    def setUp(self):
+        User.objects.create_user(self.username, self.password, gender=settings.MALE)
+
+    def test_login(self):
+        url = reverse('account_login')
+        # 正确密码
+        resp = self.client.post(url, {'username':self.username, 'password':self.password})
+        self.assertEquals(resp.status_code, 200)
+        data = json.loads(resp.content)
+        self.assertEquals(data['code'], 0)
+
+        # 错误密码
+        resp = self.client.post(url, {'username': self.username, 'password': 'err'})
+        self.assertEquals(resp.status_code, 200)
+        #print unicode(resp.content)
+        data = json.loads(resp.content)
+        self.assertEquals(data['code'], 1)
+
+        # 无效数据
+        resp = self.client.post(url, {'-': self.username})
+        #print unicode(resp.content)
+        data = json.loads(resp.content)
+        self.assertEquals(data['code'], 1)
+
+

+ 65 - 0
apps/account/tokens.py

@@ -0,0 +1,65 @@
+"""django.contrib.auth.tokens, but without using last_login in hash"""
+
+from datetime import date
+from django.conf import settings
+from django.utils.http import int_to_base36, base36_to_int
+
+class TokenGenerator(object):
+    """
+    Strategy object used to generate and check tokens
+    """
+
+    TOKEN_TIMEOUT_DAYS = getattr(settings, "TOKEN_TIMEOUT_DAYS", 7)
+
+    def make_token(self, user):
+        """
+        Returns a token for a given user
+        """
+        return self._make_token_with_timestamp(user, self._num_days(self._today()))
+
+    def check_token(self, user, token):
+        """
+        Check that a token is correct for a given user.
+        """
+        # Parse the token
+        try:
+            ts_b36, hash = token.split("-")
+        except ValueError:
+            return False
+
+        try:
+            ts = base36_to_int(ts_b36)
+        except ValueError:
+            return False
+
+        # Check that the timestamp/uid has not been tampered with
+        if self._make_token_with_timestamp(user, ts) != token:
+            return False
+
+        # Check the timestamp is within limit
+        if (self._num_days(self._today()) - ts) > self.TOKEN_TIMEOUT_DAYS:
+            return False
+
+        return True
+
+    def _make_token_with_timestamp(self, user, timestamp):
+        # timestamp is number of days since 2001-1-1.  Converted to
+        # base 36, this gives us a 3 digit string until about 2121
+        ts_b36 = int_to_base36(timestamp)
+
+        # No longer using last login time
+        import hashlib 
+        hash = hashlib.sha1(settings.SECRET_KEY + unicode(user.id) +
+            user.password + 
+            unicode(timestamp)).hexdigest()[::2]
+        return "%s-%s" % (ts_b36, hash)
+
+    def _num_days(self, dt):
+        return (dt - date(2001,1,1)).days
+
+    def _today(self):
+        # Used for mocking in tests
+        return date.today()
+
+
+token_generator = TokenGenerator()

+ 33 - 0
apps/account/urls.py

@@ -0,0 +1,33 @@
+from django.conf.urls import url
+
+from apps.account.views import *
+
+urlpatterns = (
+    url(r'^login/$', login, name='account_login'),
+
+    url(r'^group/data/$', group_list),
+    url(r'^group/save/$', group_save),
+    url(r'^group/delete/$', group_delete),
+    url(r'^group/combobox/data/$', group_combobox_list),
+
+    url(r'^permission/all/$', permission_all),
+
+    url(r'^employee/data/$', employee_list),
+    url(r'^employee/save/$', employee_save),
+    url(r'^employee/delete/$', employee_delete),
+    url(r'^employee/tree/$', employee_tree),
+    url(r'^employee/manager/save/$', manager_save),
+    url(r'^employee/combobox/data/$', employee_combobox_list),
+    url(r'^employee/data/mobile/$', employee_list_mobile),
+
+    url(r'^password/save/$', password_save),
+
+    url(r'^department/data/$', department_list),
+    url(r'^department/save/$', department_save),
+    url(r'^department/delete/$', department_delete),
+    url(r'^department/tree/$', department_tree),
+    url(r'^select_department/$', select_department),
+
+    url(r'^count/$', home_count),
+)
+

+ 480 - 0
apps/account/views.py

@@ -0,0 +1,480 @@
+#coding=utf-8
+
+import traceback
+import json
+
+from collections import OrderedDict
+from django.db import transaction,IntegrityError
+from django.db.models import F,ProtectedError
+from django.shortcuts import get_object_or_404
+from django.views.decorators.csrf import csrf_exempt
+from django.contrib.auth.models import Permission, Group
+from django.utils import timezone
+from libs import utils
+from libs.utils import dump_form_errors
+from libs.http import JSONError, JSONResponse,DataGridJSONResponse
+from apps.dashboard.forms import MyAuthenticationForm
+from decorators import token_required,permission_required
+from apps.exceptions import CustomError
+
+from apps.foundation.models import BizLog
+
+from models import User, Department,SubDepartment,SubEmployee
+from serializers import EmployeeSerializer, EmployeeSafeSerializer, GroupSerializer,GroupComboboxSerializer
+
+from django.db.models import Q
+from django.conf import settings
+from apps.goods.models import GoodsGodownEntry
+from apps.material.models import Deliver
+from apps.order.models import SaleOrder, GoodsDeliver
+from apps.plan.models import SalePlan, ProductionPlan
+from apps.purchase.models import PurchasePlan, PurchaseOrder, GodownEntry, GodownEntryReturn
+from apps.warehouse.models import Warehouse, Inventory
+from apps.office.models import Notice,NoticeBrowseRecord
+
+@csrf_exempt
+def login(request):
+    form = MyAuthenticationForm(data=request.POST, request=request)
+    if form.is_valid():
+        user = form.get_user()
+        if user.status == User.DIMISSION:
+            BizLog.objects.addnew(user, BizLog.INSERT, u"离职账号[%s]登录,IP[%s]" % (
+                user.username,
+                request.META['REMOTE_ADDR']
+            ))
+            return JSONError(u'离职账号禁止登录')
+
+        permissions = list(user.get_all_permissions())
+
+        BizLog.objects.addnew(user, BizLog.INSERT, u"[%s]登录,IP[%s]" % (
+            user.username,
+            request.META['REMOTE_ADDR']
+        ))
+
+        return JSONResponse({
+            'user_id':user.id,
+            'access_token':form.access_token,
+            'name':user.name,
+            'permissions':permissions
+        })
+    else:
+        BizLog.objects.addnew(None, BizLog.INSERT, u"[%s]登录失败,密码[%s],IP[%s]" % (
+            request.POST['username'],
+            request.POST['password'],
+            request.META['REMOTE_ADDR']
+        ))
+        return JSONError(dump_form_errors(form))
+
+@permission_required('foundation.view_group')
+def group_list(request):
+    rows = Group.objects.filter()
+
+    rows, total = utils.get_page_data(request, rows)
+    serializer = GroupSerializer(rows, many=True)
+
+    return DataGridJSONResponse(serializer.data, total)
+
+@token_required
+def group_combobox_list(request):
+    rows = Group.objects.filter()
+
+    total = rows.count()
+    #rows, total = utils.get_page_data(request, rows)
+    serializer = GroupComboboxSerializer(rows, many=True)
+
+    return DataGridJSONResponse(serializer.data, total)
+
+@csrf_exempt
+@permission_required('foundation.add_group')
+def group_save(request):
+    id = request.GET.get('id')
+    data = json.loads(request.body)
+
+    try:
+        with transaction.atomic():
+            User.objects.save_group(id, data['name'], data['permissions'], request.user)
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'保存失败')
+    return JSONResponse()
+
+@csrf_exempt
+@permission_required('foundation.delete_group')
+def group_delete(request):
+    id = request.GET.get('id')
+
+    try:
+        with transaction.atomic():
+            group = Group.objects.filter(pk=id).first()
+            if not group:
+                raise CustomError(u'未找到相应的权限组')
+            BizLog.objects.addnew(request.user, BizLog.DELETE, u"删除权限组[%s],id=%s" % (group.name, id))
+            group.delete()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except ProtectedError:
+        return JSONError(u'该权限组已分配给用户,禁止删除!')
+    except IntegrityError:
+        return JSONError(u'该权限组已分配给用户,禁止删除!')
+    except:
+        traceback.print_exc()
+        return JSONError(u'删除失败')
+    return JSONResponse()
+
+@token_required
+def permission_all(request):
+    rows = Permission.objects.all().exclude(name__startswith='Can')
+    rows = User.objects.sort_perms(rows)
+
+    menus = OrderedDict()
+    for row in rows:
+        item = {'id': row.id, 'name': row.name}
+        mn = User.objects.get_menuname_of_contenttype(row.content_type.app_label, row.content_type.model)
+        if menus.has_key(mn):
+            permissions = menus[mn]
+        else:
+            permissions = menus[mn] = OrderedDict()
+
+        if permissions.has_key(row.content_type.name):
+            if not item in permissions[row.content_type.name]:
+                permissions[row.content_type.name].append(item)
+        else:
+            permissions[row.content_type.name] = [item, ]
+    return JSONResponse(menus)
+
+@permission_required('account.view_user')
+def employee_list(request):
+    username = request.GET.get('username')
+    name = request.GET.get('name')
+
+    rows = User.objects.filter()
+    if username:
+        rows = rows.filter(username__icontains=username)
+    if name:
+        rows = rows.filter(name__icontains=name)
+    rows, total = utils.get_page_data(request, rows)
+    serializer = EmployeeSafeSerializer(rows, many=True)
+    return DataGridJSONResponse(serializer.data, total)
+
+@csrf_exempt
+@permission_required('account.add_user')
+def employee_save(request):
+    id = request.GET.get('id')
+    data = json.loads(request.body)
+
+    try:
+        with transaction.atomic():
+            serializer = EmployeeSerializer.factory(request.user, data, id)
+            if serializer.instance:
+                user = serializer.instance
+                if not data['password']:
+                    data['password'] = user.password
+                else:
+                    user.set_password(data['password'])
+                    data['password'] = user.password
+            serializer.validSave()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'保存失败')
+    return JSONResponse()
+
+@csrf_exempt
+@token_required
+def employee_tree(request):
+    def child_employee_tree(department_id,children):
+        rows = User.objects.filter(department_id=department_id)
+        for row in rows:
+            item = {
+                'id': row.id,
+                'value': 'employee_' + str(row.id),
+                'name': row.name
+            }
+            children.append(item)
+
+    def child_department_tree(parent_id,children):
+        rows = Department.objects.filter(parent_id=parent_id)
+        for row in rows:
+            item = {
+                'id': row.id,
+                'value': 'department_' + str(row.id),
+                'name': row.name,
+                'children': []
+            }
+            child_employee_tree(row.id,item['children'])
+            child_department_tree(row.id,item['children'])
+            children.append(item)
+
+    result = []
+    child_department_tree(None,result)
+    return JSONResponse(result)
+
+@csrf_exempt
+@permission_required('account.add_user')
+def manager_save(request):
+    id = request.GET.get('id')
+    data = json.loads(request.body)
+    try:
+        with transaction.atomic():
+            user = User.getById(id)
+            user.removeSubs()
+
+            for row in data['managers']:
+                sub_arr = row.split('_')
+                if sub_arr[0] == 'department':
+                    SubDepartment.objects.create(user=user,department_id=int(sub_arr[1]))
+                elif sub_arr[0] == 'employee':
+                    SubEmployee.objects.create(user=user,employee_id=int(sub_arr[1]))
+            BizLog.objects.addnew(request.user, BizLog.UPDATE, u"修改员工管理范围[%s],id=%s" % (user.name, user.id),data)
+        return JSONResponse({})
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception,e:
+        traceback.print_exc()
+        return JSONError(u'保存失败')
+
+@csrf_exempt
+@permission_required('account.delete_user')
+def employee_delete(request):
+    id = request.GET.get('id')
+    try:
+        with transaction.atomic():
+            user = User.getById(id)
+            BizLog.objects.addnew(request.user, BizLog.DELETE, u"删除员工[%s],id=%d" % (user.name, user.id))
+            user.removeSubs()
+            user.delete()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except ProtectedError:
+        return JSONError(u'该员工存在业务数据,禁止删除!')
+    except IntegrityError:
+        return JSONError(u'该员工存在业务数据,禁止删除!')
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'删除失败!')
+    return JSONResponse({})
+
+@token_required
+def employee_combobox_list(request):
+    rows = User.objects.filter(status=User.INSERVICE)
+    total = rows.count()
+    #rows, total = utils.get_page_data(request, rows)
+    serializer = EmployeeSafeSerializer(rows, many=True)
+    return DataGridJSONResponse(serializer.data, total)
+
+@token_required
+def employee_list_mobile(request):
+    id = request.GET.get('id')
+    user = User.objects.filter(id=id).first()
+    data = {
+        'name': user.name,
+        'tel': user.tel,
+        'department_name': user.department.name
+    }
+    return JSONResponse(data)
+
+@csrf_exempt
+@token_required
+def password_save(request):
+    data = json.loads(request.body)
+    try:
+        data['new_password'] = data['new_password'].strip(u' ')
+        data['confirm_password'] = data['confirm_password'].strip(u' ')
+        data['old_password'] = data['old_password'].strip(u' ')
+
+        if data['new_password'] != data['confirm_password']:
+            raise CustomError(u'两次输入的密码不一致, 请检查')
+
+        with transaction.atomic():
+            if not request.user.check_password(data['old_password']):
+                raise CustomError(u'原密码输入错误, 请检查')
+            request.user.set_password(data['new_password'])
+            request.user.save()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'保存失败!')
+    return JSONResponse()
+
+@permission_required('account.view_department')
+def department_list(request):
+    result = []
+    rows = Department.objects.filter()
+    for row in rows:
+        item = {
+            'id': row.id,
+            'name': row.name,
+            'notes': row.notes,
+            'parent_id': row.parent_id or 0,
+        }
+        result.append(item)
+    return JSONResponse(result)
+
+@csrf_exempt
+@permission_required('account.add_department')
+def department_save(request):
+    id = request.GET.get('id')
+    parent_id = request.GET.get('parent_id')
+    data = json.loads(request.body)
+
+    try:
+        with transaction.atomic():
+            if id:
+                dep = Department.getById(id)
+                dep.name = data['name']
+                dep.notes = data['notes']
+                dep.save()
+                BizLog.objects.addnew(request.user, BizLog.UPDATE, u"修改部门[%s],id=%d" % (data['name'], dep.id))
+            else:
+                if parent_id:
+                    parent = Department.getById(parent_id)
+                    parent_id = parent.id
+                else:
+                    parent = None
+                    parent_id = None
+                lft = Department.getLft(parent)
+
+                Department.objects.filter(rgt__gt=lft).update(rgt=F('rgt') + 2)
+                Department.objects.filter(lft__gt=lft).update(lft=F('lft') + 2)
+
+                department = Department.objects.create(
+                    name=data['name'],
+                    notes=data['notes'],
+                    parent_id=parent_id,
+                    lft=lft+1,
+                    rgt=lft+2
+                )
+
+                BizLog.objects.addnew(request.user, BizLog.INSERT, u"添加部门[%s],id=%d" % (data['name'], department.id))
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'保存失败!')
+    return JSONResponse({})
+
+@csrf_exempt
+@permission_required('account.delete_department')
+def department_delete(request):
+    id = request.GET.get('id')
+
+    try:
+        with transaction.atomic():
+            if Department.objects.filter(parent_id=id).count() > 0:
+                raise CustomError(u'该部门存在子部门, 不允许删除')
+
+            dep = Department.getById(id)
+            lft = dep.lft
+            rgt = dep.rgt
+
+            total = rgt - lft + 1
+
+            BizLog.objects.addnew(request.user, BizLog.DELETE, u"删除部门[%s],id=%d" % (dep.name, dep.id))
+
+            dep.delete()
+            Department.objects.filter(rgt__gt=lft).update(rgt=F('rgt')-total)
+            Department.objects.filter(lft__gt=lft).update(lft=F('lft')-total)
+
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except ProtectedError:
+        return JSONError(u'该部门已被引用,禁止删除!')
+    except IntegrityError:
+        return JSONError(u'该部门已被引用,禁止删除!')
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'删除失败!')
+    return JSONResponse({})
+
+@csrf_exempt
+@token_required
+def department_tree(request):
+    def child_department_tree(parent_id,children):
+        rows = Department.objects.filter(parent_id=parent_id)
+        for row in rows:
+            item = {
+                'id': row.id,
+                'value': row.id,
+                'name': row.name,
+                'notes': row.notes,
+                'parent_id': row.parent_id,
+                'children': []
+            }
+            child_department_tree(row.id,item['children'])
+            children.append(item)
+
+    result = []
+    child_department_tree(None,result)
+    return JSONResponse(result)
+
+@csrf_exempt
+@token_required
+def select_department(request):
+    result = []
+    rows = Department.objects.filter()
+    for row in rows:
+        item = {
+            'id': row.id,
+            'name': row.name
+        }
+        result.append(item)
+    return JSONResponse(result)
+
+@token_required
+def home_count(request):
+    department_ids = request.user.getSubDepartmentIds()
+    user_ids = request.user.getSubEmployeeIds()
+    warehouses_ids = Warehouse.getManagerWarehouses(request.user)
+    sale_plan_count = SalePlan.objects.filter(Q(create_user_id__in=user_ids) | Q(department_id__in=department_ids) | Q(create_user=request.user)).filter(status=settings.DEFAULT).count()
+    sale_order_count = SaleOrder.objects.filter(Q(department_id__in=department_ids) | Q(create_user_id__in=user_ids) | Q(create_user=request.user)).filter(status=settings.DEFAULT).count()
+    production_count = ProductionPlan.objects.filter(Q(department_id__in=department_ids) | Q(create_user_id__in=user_ids) | Q(create_user=request.user)).filter(status=settings.DEFAULT).count()
+    purchase_count = PurchasePlan.objects.filter(Q(department_id__in=department_ids) | Q(create_user_id__in=user_ids) | Q(create_user=request.user)).filter(status=settings.DEFAULT).count()
+    purchase_order_count = PurchaseOrder.objects.filter(Q(department_id__in=department_ids) | Q(create_user_id__in=user_ids) | Q(create_user=request.user)).filter(status=settings.DEFAULT).count()
+
+    material_godownentry_count = GodownEntry.objects.filter(product_type=GodownEntry.MATERIAL,warehouse_id__in=warehouses_ids).filter(Q(department_id__in=department_ids) | Q(create_user_id__in=user_ids) | Q(create_user=request.user)).filter(status=settings.DEFAULT).count()
+    material_deliver_count = Deliver.objects.filter(product_type=GodownEntry.MATERIAL,warehouse_id__in=warehouses_ids).filter(Q(department_id__in=department_ids) | Q(create_user_id__in=user_ids) | Q(create_user=request.user)).filter(status=settings.DEFAULT).count()
+    material_godownentry_return_count = GodownEntryReturn.objects.filter(type=GodownEntryReturn.MATERIAL, warehouse_id__in=warehouses_ids).filter(Q(department_id__in=department_ids) | Q(create_user_id__in=user_ids) | Q(create_user=request.user)).filter(status=settings.DEFAULT).count()
+    material_inventory_count = Inventory.objects.filter(product_type=Inventory.MATERIAL,warehouse_id__in=warehouses_ids).filter(Q(department_id__in=department_ids) | Q(create_user_id__in=user_ids) | Q(create_user=request.user)).filter(check_status=settings.DEFAULT).count()
+
+    consumable_godownentry_count = GodownEntry.objects.filter(product_type=GodownEntry.CONSUMABLE,warehouse_id__in=warehouses_ids).filter(Q(department_id__in=department_ids) | Q(create_user_id__in=user_ids) | Q(create_user=request.user)).filter(status=settings.DEFAULT).count()
+    consumable_deliver_count = Deliver.objects.filter(product_type=GodownEntry.CONSUMABLE,warehouse_id__in=warehouses_ids).filter(Q(department_id__in=department_ids) | Q(create_user_id__in=user_ids) | Q(create_user=request.user)).filter(status=settings.DEFAULT).count()
+    consumable_godownentry_return_count = GodownEntryReturn.objects.filter(type=GodownEntryReturn.CONSUMABLE, warehouse_id__in=warehouses_ids).filter(Q(department_id__in=department_ids) | Q(create_user_id__in=user_ids) | Q(create_user=request.user)).filter(status=settings.DEFAULT).count()
+    consumable_inventory_count = Inventory.objects.filter(product_type=Inventory.CONSUMABLE,warehouse_id__in=warehouses_ids).filter(Q(department_id__in=department_ids) | Q(create_user_id__in=user_ids) | Q(create_user=request.user)).filter(check_status=settings.DEFAULT).count()
+
+    goods_godownentry_count = GoodsGodownEntry.objects.filter(warehouse_id__in=warehouses_ids).filter(Q(department_id__in=department_ids) | Q(create_user_id__in=user_ids) | Q(create_user=request.user)).filter(status=settings.DEFAULT).count()
+    goods_deliver_count = GoodsDeliver.objects.filter(warehouse_id__in=warehouses_ids).filter(Q(department_id__in=department_ids) | Q(create_user_id__in=user_ids) | Q(create_user=request.user)).filter(status=settings.DEFAULT).count()
+    goods_inventory_count = Inventory.objects.filter(product_type=Inventory.GOODS,warehouse_id__in=warehouses_ids).filter(Q(department_id__in=department_ids) | Q(create_user_id__in=user_ids) | Q(create_user=request.user)).filter(check_status=settings.DEFAULT).count()
+
+    children = request.user.getSubDepartmentIds()
+    children.append(request.user.department.id)
+    rows = Notice.objects.filter(dendline__gte=timezone.now(), department__in=children)
+    browsed_ids = NoticeBrowseRecord.objects.filter(browse_user_id=request.user).values_list('notice_id', flat=True)
+    not_notices = rows.filter(~Q(id__in=browsed_ids))
+
+    data = {
+        'notice_unread': not_notices.count(),
+        'sale_plan_count': sale_plan_count,
+        'sale_order_count': sale_order_count,
+        'production_count': production_count,
+        'purchase_count': purchase_count,
+        'purchase_order_count': purchase_order_count,
+
+        'material_godownentry_count': material_godownentry_count,
+        'material_deliver_count': material_deliver_count,
+        'material_godownentry_return_count': material_godownentry_return_count,
+        'material_inventory_count': material_inventory_count,
+
+        'consumable_godownentry_count': consumable_godownentry_count,
+        'consumable_deliver_count': consumable_deliver_count,
+        'consumable_godownentry_return_count': consumable_godownentry_return_count,
+        'consumable_inventory_count': consumable_inventory_count,
+
+        'goods_godownentry_count': goods_godownentry_count,
+        'goods_deliver_count': goods_deliver_count,
+        'goods_inventory_count': goods_inventory_count,
+    }
+    return JSONResponse(data)

+ 400 - 0
apps/base.py

@@ -0,0 +1,400 @@
+#coding=utf-8
+
+import datetime
+import re
+
+import tablib
+
+from libs.utils import strfdate
+from libs import utils
+
+try:
+    from openpyxl.utils.exceptions import InvalidFileException
+except:
+    from tablib.packages.openpyxl.shared.exc import InvalidFileException
+
+from apps.exceptions import CustomError
+
+DATETIME_FORMAT = '%Y-%m-%d %H:%M'
+DATETIME_FORMAT1 = '%Y-%m-%d %H:%M:%S'
+DATE_FORMAT = '%Y-%m-%d'
+
+class Formater():
+    @staticmethod
+    def formatStr(value):
+        res = u''
+        if value != None:
+            try:
+                res = unicode(value)
+            except:
+                pass
+        return res
+
+    @staticmethod
+    def formatStrTime(value):
+        res = u''
+        if value != None:
+            if isinstance(value, datetime.datetime):
+                res = value.strftime('%Y-%m-%d %H:%M')
+            elif isinstance(value, unicode):
+                res = value
+        return res
+
+    @staticmethod
+    def formatStrTimeS(value):
+        res = u''
+        if value != None:
+            if isinstance(value, datetime.datetime):
+                res = value.strftime('%Y-%m-%d %H:%M:%S')
+            elif isinstance(value, unicode):
+                res = value
+        return res
+
+    @staticmethod
+    def formatStrDate(value):
+        res = u''
+        if value != None:
+            if isinstance(value, datetime.date):
+                res = strfdate(value)
+            elif isinstance(value, unicode):
+                res = value
+        return res
+
+    @staticmethod
+    def formatCount(value):
+        return long(round(float(value or 0) * 100,0))
+
+    @staticmethod
+    def formatPrice(value):
+        return long(round(float(value or 0) * 100,0))
+
+    @staticmethod
+    def formatCountShow(value):
+        return '%.2f' % round(float(value or 0)/100.0,2)
+
+    @staticmethod
+    def formatPriceShow(value):
+        return '%.2f' % round(float(value or 0)/100.0,2)
+
+    @staticmethod
+    def formatEmptyPriceShow(value):
+        if value == None or value == '':
+            return ''
+        return '%.2f' % round(float(value or 0) / 100.0, 2)
+
+    @staticmethod
+    def formatAmount(value):
+        return long(round(float(value or 0) * 10000,0))
+
+    @staticmethod
+    def formatAmountShow(value):
+        return '%.4f' % round(float(value or 0) / 10000.0 + 0.0000001, 2)
+
+    @staticmethod
+    def formatAmountShowWithTwoDecimalPlaces(value):
+        return '%.2f' % round(float(value or 0) / 10000.0 + 0.0000001, 2)
+
+    @staticmethod
+    def formatEmptyAmountShow(value):
+        if value == None or value == '':
+            return ''
+        return '%.4f' % round(float(value or 0) / 10000.0 + 0.0000001, 2)
+
+    @staticmethod
+    def formatAmountCNY(value):
+        return utils.number2CNY(Formater.formatAmountShow(value))
+
+class CustomFormaterByUser():
+    @staticmethod
+    def formatEmptyStr(value,user):
+        return Formater.formatEmptyStr(value)
+
+    @staticmethod
+    def formatEmptyFloat(value, user):
+        #return round(value or 0, 2)
+        return Formater.formatEmptyFloat(value)
+
+    @staticmethod
+    def formatTime(value,user):
+        return Formater.formatTime(value)
+
+    @staticmethod
+    def formatDate(value, user):
+        return Formater.formatDate(value)
+
+    @staticmethod
+    def formatCountShow(value,user):
+        return Formater.formatCountShow(value)
+    @staticmethod
+    def formatPartAmountShow(value, user):
+        #from apps.foundation.models import Config
+        #num = Config.objects.get_proprty(user.company.id, Config.KEY_PART_ENTRY_PRICE_ROUND)
+        #if not num:
+        #    num = 4
+        #return round(float(value or 0) / 1000000, int(num))
+        return Formater.formatAmountShow(value)
+
+class ExcelImporter():
+    def getExcelData(self,file):
+        if not file:
+            raise CustomError(u'请上传数据文件')
+        try:
+            data = tablib.import_set(file.read(), format='xlsx').dict
+            if not len(data):
+                raise CustomError(u'上传的文件内没有发现数据')
+            return data
+        except InvalidFileException:
+            raise CustomError(u'请上传<strong>xlsx</strong>格式的数据文件,老版本的xls格式不被支持!')
+
+    def validRow(self,row):
+        data = {}
+        for (k,v) in self.fields.items():
+            is_required = v[0]
+            format_proc = v[1]
+
+            try:
+                value = row[k]
+            except:
+                raise CustomError(u'缺少[%s]列,请检查模板文件或重新下载' % k)
+
+            if is_required and value == None:
+                raise CustomError(u'%s:为必填项' % k)
+
+            if format_proc and value != None:
+                try:
+                    value = format_proc(value)
+                except CustomError,e:
+                    raise CustomError(u'%s:错误的值[%s]' % (k,e.get_error_msg()))
+
+            data[k] = value
+        return data
+
+    @staticmethod
+    def formatUnicode(value):
+        try:
+            return unicode(value).strip(u' ')
+        except:
+            return ''
+
+    @staticmethod
+    def formatDateTime(value):
+        try:
+            res = datetime.datetime.strptime(value, '%Y-%m-%d %H:%M:%S')
+        except:
+            try:
+                res = datetime.datetime.strptime(value, '%Y-%m-%d')
+            except:
+                raise CustomError(u'格式为:2018-01-01 或 2018-01-01 00:00:00')
+        return res
+
+    @staticmethod
+    def formatInt(value):
+        try:
+            return int(float(unicode(value).strip(u' ')))
+        except:
+            raise CustomError(u'不能转换为整数')
+
+    @staticmethod
+    def formatFloat(value):
+        try:
+            return round(float(unicode(value).strip(u' ')) or 0,2)
+        except:
+            raise CustomError(u'不能转换为小数')
+
+    @staticmethod
+    def formatFloatGtZ(value):
+        v = ExcelImporter.formatFloat(value)
+        if v <= 0:
+            raise CustomError(u'必须大于0')
+        return v
+
+    @staticmethod
+    def formatFloatGeZ(value):
+        v = ExcelImporter.formatFloat(value)
+        if v < 0:
+            raise CustomError(u'必须大于等于0')
+        return v
+
+    @staticmethod
+    def formatTel(value):
+        try:
+            value = unicode(value).strip(u' ')
+        except:
+            value = ''
+        if value == '':
+            return value
+        is_mobile = re.match(r'^0?(13[0-9]|14[0-9]|15[0-9]|17[0-9]|18[0-9]|19[0-9]|166)[0-9]{8}$', value)
+        is_tel = re.match(r'^([0-9]{3,4}-)?[0-9]{7,8}$', value)
+        if not (is_mobile or is_tel):
+            raise CustomError(u'不合法的手机号或者固话号')
+        return value
+
+class Filter():
+    @staticmethod
+    def limit(rows, start, end):
+        if start and end:
+            rows = rows[start:end]
+        else:
+            if start:
+                rows = rows[start:]
+            elif end:
+                rows = rows[:end]
+        return rows
+
+class Shower():
+    def __init__(self, fields_map, show_data):
+        if isinstance(show_data, dict):
+            self.show_type = 'dict'
+            self.fields,self.kips = Shower.getFkips(fields_map, show_data)
+        elif isinstance(show_data, (list, tuple)):
+            self.show_type = 'arr'
+            self.fields, self.ips = Shower.getFips(fields_map, show_data)
+
+    def getNeedFields(self):
+        return self.fields
+
+    def format(self, rows,start,end):
+        rows = Filter.limit(rows,start,end)
+        if self.show_type == 'dict':
+            return self.formatDict(rows)
+        elif self.show_type == 'arr':
+            return self.formatArr(rows)
+        return []
+
+    def formatArr(self,rows):
+        data = []
+        for row in rows:
+            item = []
+            for ip in self.ips:
+                if ip[1]:
+                    value = ip[1](row[ip[0]])
+                else:
+                    value = row[ip[0]]
+                item.append(value)
+            data.append(item)
+        return data
+
+    def formatDict(self,rows):
+        data = []
+        for row in rows:
+            item = {}
+            for kip in self.kips:
+                if kip[2]:
+                    value = kip[2](row[kip[1]])
+                else:
+                    value = row[kip[1]]
+                item[kip[0]] = value
+            data.append(item)
+        return data
+
+    @staticmethod
+    def getFips(fields_map, show_arr):
+        fields = []
+        ips = []
+        index = 0
+
+        for item in show_arr:
+            field = Shower.getFieldByKey(fields_map,item)
+            if field:
+                fields.append(field[0])
+                ip = (index, field[1])
+                ips.append(ip)
+                index += 1
+        return fields, ips
+
+    @staticmethod
+    def getFkips(fields_map, show_map):
+        fields = []
+        kips = []
+        index = 0
+
+        for (k,v) in show_map.items():
+            field = Shower.getFieldByKey(fields_map,v)
+            if field:
+                fields.append(field[0])
+                kip = (k, index, field[1])
+                kips.append(kip)
+                index += 1
+        return fields, kips
+
+    @staticmethod
+    def getFieldByKey(fields_map,key):
+        try:
+            return fields_map[key]
+        except:
+            return None
+
+class CustomShower(Shower):
+    def __init__(self, fields_map, show_data, user):
+        Shower.__init__(self,fields_map,show_data)
+        self.user = user
+
+    def format(self, rows,start,end):
+        rows = Filter.limit(rows,start,end)
+        if self.show_type == 'dict':
+            return self.formatDict(rows)
+        elif self.show_type == 'arr':
+            return self.formatArr(rows)
+        return []
+
+    def getNeedFields(self):
+        return self.fields
+
+    def formatArr(self,rows):
+        data = []
+        for row in rows:
+            item = []
+            for ip in self.ips:
+                if ip[1]:
+                    value = ip[1](row[ip[0]],self.user)
+                else:
+                    value = row[ip[0]]
+                item.append(value)
+            data.append(item)
+        return data
+
+    def formatDict(self,rows):
+        data = []
+        for row in rows:
+            item = {}
+            for kip in self.kips:
+                if kip[2]:
+                    value = kip[2](row[kip[1]],self.user)
+                else:
+                    value = row[kip[1]]
+                item[kip[0]] = value
+            data.append(item)
+        return data
+
+    def formatAmountShow(self,value):
+        #from apps.foundation.models import Config
+        #num = Config.objects.get_proprty(self.user.company.id, Config.KEY_PART_ENTRY_PRICE_ROUND)
+        #if not num:
+        #    num = 4
+        #return round(float(value or 0) / 1000000, int(num))
+        return Formater.formatAmountShow(value)
+
+
+def clean_datetime_range(data, fieldname, source=None):
+    if data is not None and fieldname in data and data[fieldname] != '':
+        if data is not None and source in data and data[source] == 'touch':
+            t = data[fieldname].split('T')[0]
+            data = data.copy()
+            data[fieldname + '_0'] = t
+            data[fieldname + '_1'] = t + ' 23:59:59'
+        else:
+            t = data[fieldname].split(' - ')
+            data = data.copy()
+            data[fieldname+'_0'] = t[0]
+            data[fieldname+'_1'] = t[1] + ' 23:59:59'
+        data.pop(fieldname)
+    return data
+
+def clean_date_range(data, fieldname):
+    if data is not None and fieldname in data and data[fieldname] != '':
+        t = data[fieldname].split(' - ')
+        data = data.copy()
+        data[fieldname+'_0'] = t[0]
+        data[fieldname+'_1'] = t[1]
+        data.pop(fieldname)
+    return data

+ 0 - 0
apps/config/__init__.py


+ 12 - 0
apps/config/filters.py

@@ -0,0 +1,12 @@
+# coding=utf-8
+
+import django_filters
+
+from apps.config.models import Config
+
+
+class ConfigFilter(django_filters.FilterSet):
+
+    class Meta:
+        model = Config
+        fields = '__all__'

+ 0 - 0
apps/config/migrations/__init__.py


+ 37 - 0
apps/config/models.py

@@ -0,0 +1,37 @@
+# coding=utf-8
+
+from django.db import models
+
+class Config(models.Model):
+    KEY_CONSIGNEE_NAME = "consignee_name"       # 收货人姓名
+    KEY_CONSIGNEE_TEL = "consignee_tel"         # 收货人电话
+
+    property = models.CharField(max_length=100, verbose_name=u'属性')
+    value = models.TextField(verbose_name=u'值')
+
+    class Meta:
+        db_table = "system_config"
+        verbose_name = u"基础设置"
+        index_together = (
+            'property',
+        )
+        default_permissions = ()
+        permissions = (
+            ("edit_config", u"修改"),
+        )
+
+    @staticmethod
+    def getConsigneeName():
+        try:
+            row = Config.objects.get(property=Config.KEY_CONSIGNEE_NAME)
+            return row.value
+        except:
+            return ""
+
+    @staticmethod
+    def getConsigneeTel():
+        try:
+            row = Config.objects.get(property=Config.KEY_CONSIGNEE_TEL)
+            return row.value
+        except:
+            return ""

+ 10 - 0
apps/config/serializers.py

@@ -0,0 +1,10 @@
+# coding=utf-8
+
+from rest_framework import serializers
+from apps.config.models import Config
+
+class ConfigSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = Config
+        fields = '__all__'

+ 9 - 0
apps/config/urls.py

@@ -0,0 +1,9 @@
+# coding=utf-8
+from django.conf.urls import url
+
+from views import *
+
+urlpatterns = (
+    url(r'^data/$', config_list),
+    url(r'^save/$', config_save),
+)

+ 46 - 0
apps/config/views.py

@@ -0,0 +1,46 @@
+# coding=utf-8
+
+import traceback
+import json
+
+from django.views.decorators.csrf import csrf_exempt
+from django.db import transaction
+
+from apps.exceptions import CustomError
+from apps.foundation.models import BizLog
+from libs.http import JSONResponse, JSONError, DataGridJSONResponse
+from apps.account.decorators import permission_required, token_required
+from models import Config
+from serializers import  ConfigSerializer
+
+@csrf_exempt
+@permission_required('config.edit_config')
+def config_save(request):
+    data = json.loads(request.POST.get('data'))
+
+    try:
+        with transaction.atomic():
+            for item in data:
+                if not item['value']:
+                    continue
+
+                config = Config.objects.filter(property=item['key']).first()
+                if config:
+                    config.value = item['value']
+                    config.save()
+                else:
+                    Config.objects.create(property=item['key'], value=item['value'])
+
+            BizLog.objects.addnew(request.user, BizLog.UPDATE, u"修改系统配置", data)
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        return JSONError(u'保存失败!')
+    return JSONResponse()
+
+@csrf_exempt
+@token_required
+def config_list(request):
+    rows = Config.objects.filter()
+    serializer = ConfigSerializer(rows, many=True)
+    return JSONResponse(serializer.data)

+ 0 - 0
apps/customer/__init__.py


+ 13 - 0
apps/customer/filters.py

@@ -0,0 +1,13 @@
+#coding=utf-8
+
+import django_filters
+from models import  Customer
+
+
+class CustomerFilter(django_filters.FilterSet):
+    name = django_filters.CharFilter(name='name', lookup_expr='icontains')
+    mobile = django_filters.CharFilter(name='mobile', lookup_expr='icontains')
+
+    class Meta:
+        model = Customer
+        fields = ['name', 'mobile']

+ 0 - 0
apps/customer/migrations/__init__.py


+ 44 - 0
apps/customer/models.py

@@ -0,0 +1,44 @@
+#coding=utf-8
+
+from django.db import models
+from django.utils import timezone
+
+from apps.exceptions import CustomError
+from apps.account.models import User
+
+class Customer(models.Model):
+    name = models.CharField(max_length=100, verbose_name=u"姓名")
+    mobile = models.CharField(max_length=12, verbose_name=u"手机号")
+    company_name = models.CharField(max_length=100, verbose_name=u"公司名称",blank=True,null=True)
+    company_tel = models.CharField(max_length=100, verbose_name=u"公司电话",blank=True,null=True)
+    opening_bank = models.CharField(max_length=100, verbose_name=u"开户行", null=True, blank=True)
+    account = models.CharField(max_length=100, verbose_name=u"账号", null=True, blank=True)
+    credit_code = models.CharField(max_length=100, verbose_name=u"信用代码", null=True, blank=True)
+    address = models.CharField(max_length=100, verbose_name=u"地址", null=True, blank=True)
+    notes = models.CharField(max_length=200, verbose_name=u"备注", blank=True, null=True)
+    create_time = models.DateTimeField(verbose_name=u"创建时间", default=timezone.now)
+    create_user = models.ForeignKey(User, verbose_name=u"创建人", on_delete=models.PROTECT, editable=False)
+
+    class Meta:
+        db_table = "customer"
+        verbose_name = u"客户管理"
+        ordering = ('-id',)
+        index_together = (
+            'name',
+            'mobile',
+        )
+        default_permissions = ()
+        permissions = (
+            ("view_customer", u"浏览"),
+            ("add_customer", u"添加"),
+            ("import_customer", u"导入"),
+            ("export_customer", u"导出"),
+            ("delete_customer", u"删除"),
+        )
+
+    @staticmethod
+    def getById(id):
+        instance = Customer.objects.filter(pk=id).first()
+        if not instance:
+            raise CustomError(u'未找到相应的客户')
+        return instance

+ 44 - 0
apps/customer/resources.py

@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+
+from import_export import resources
+from import_export.fields import Field
+from apps.base import ExcelImporter
+
+
+class CustomerResource(resources.Resource):
+
+    def __init__(self):
+        super(CustomerResource, self).__init__()
+        self.fields['name'] = Field(attribute='name')
+        self.fields['mobile'] = Field(attribute='mobile')
+        self.fields['company_name'] = Field(attribute='company_name')
+        self.fields['company_tel'] = Field(attribute='company_tel')
+        self.fields['opening_bank'] = Field(attribute='opening_bank')
+        self.fields['account'] = Field(attribute='account')
+        self.fields['credit_code'] = Field(attribute='credit_code')
+        self.fields['address'] = Field(attribute='address')
+        self.fields['notes'] = Field(attribute='notes')
+        self.fields['create_time'] = Field(attribute='create_time')
+        self.fields['create_user_text'] = Field(attribute='create_user_text')
+
+
+    def get_export_headers(self):
+        return [u'姓名', u'手机', u'公司名称', u'公司电话', u'开户行', u'账号', u'信用代码', u'地址', u'备注', u'创建时间', u'创建人']
+
+    class Meta:
+        export_order = ('name', 'mobile', 'company_name', 'company_tel', 'opening_bank', 'account', 'credit_code', 'address', 'notes', 'create_time', 'create_user_text')
+
+
+class CustomerImporter(ExcelImporter):
+    fields = {
+        u'姓名': (True, ExcelImporter.formatUnicode),
+        u'手机号': (True, ExcelImporter.formatTel),
+        u'公司名称': (False, ExcelImporter.formatUnicode),
+        u'公司电话': (False, ExcelImporter.formatTel),
+        u'开户行': (False, ExcelImporter.formatUnicode),
+        u'账号': (False, ExcelImporter.formatUnicode),
+        u'信用代码': (False, ExcelImporter.formatUnicode),
+        u'地址': (False, ExcelImporter.formatUnicode),
+        u'备注': (False, ExcelImporter.formatUnicode),
+    }

+ 57 - 0
apps/customer/serializers.py

@@ -0,0 +1,57 @@
+#coding=utf-8
+from django.db.models import Q
+from django.utils import timezone
+from rest_framework import serializers
+
+from apps.exceptions import CustomError
+from apps.serializer_errors import dump_serializer_errors
+from models import Customer
+
+from apps.foundation.models import BizLog
+from apps import base
+
+class CustomerSerializer(serializers.ModelSerializer):
+
+    create_time = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    create_user_text = serializers.CharField(source='create_user.name', read_only=True)
+
+    class Meta:
+        model = Customer
+        fields = '__all__'
+
+    @staticmethod
+    def factory(user, data, id=None):
+        if id:
+            instance = Customer.getById(id)
+        else:
+            instance = None
+        serializer = CustomerSerializer(instance, data=data)
+        serializer.user = user
+        return serializer
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+    def create(self, validated_data):
+        validated_data['create_user'] = self.user
+        item = Customer.objects.filter(mobile=validated_data['mobile']).first()
+        if item:
+            raise CustomError(u'手机号[%s]已存在' % validated_data['mobile'])
+
+        instance = super(CustomerSerializer, self).create(validated_data)
+        BizLog.objects.addnew(self.user, BizLog.INSERT, u"添加客户档案[%s],id=%d" % (
+            instance.name, instance.id), validated_data)
+        return instance
+
+    def update(self, instance, validated_data):
+        item = Customer.objects.filter(Q(mobile=validated_data['mobile']), ~Q(id=instance.id)).first()
+        if item:
+            raise CustomError(u'手机号[%s]已存在' % validated_data['mobile'])
+
+        BizLog.objects.addnew(self.user, BizLog.UPDATE, u"修改供客户档案[%s],id=%d" % (
+            instance.name, instance.id), validated_data)
+        instance = super(CustomerSerializer, self).update(instance, validated_data)
+        return instance

+ 15 - 0
apps/customer/urls.py

@@ -0,0 +1,15 @@
+#coding=utf-8
+
+from django.conf.urls import url
+
+from views import *
+
+urlpatterns = (
+    url(r'^customer/data/$', customer_list),
+    url(r'^customer/export/$', customer_export),
+    url(r'^customer/save/$', customer_save),
+    url(r'^customer/delete/$', customer_delete),
+    url(r'^customer/import/$', customer_import),
+
+    url(r'^customer/select/$', customer_select),
+)

+ 134 - 0
apps/customer/views.py

@@ -0,0 +1,134 @@
+# coding=utf-8
+
+import traceback
+import json
+
+from django.views.decorators.csrf import csrf_exempt
+from django.db import transaction,IntegrityError
+from django.db.models import ProtectedError
+from django.db import transaction
+from django.db.models import Q
+
+from apps.exceptions import ExportChange, CustomError
+
+from apps.foundation.models import BizLog
+from libs.http import JSONResponse, JSONError, DataGridJSONResponse
+from libs import utils
+from apps.account.decorators import permission_required, token_required
+from models import Customer
+from serializers import CustomerSerializer
+from filters import CustomerFilter
+from resources import CustomerResource, CustomerImporter
+
+
+@csrf_exempt
+@permission_required('customer.view_customer')
+def customer_list(request):
+    f = CustomerFilter(request.GET, queryset=Customer.objects.all())
+    rows, total = utils.get_page_data(request, f.qs)
+    serializer = CustomerSerializer(rows, many=True)
+    return DataGridJSONResponse(serializer.data, total)
+
+
+@csrf_exempt
+@permission_required('customer.export_customer')
+def customer_export(request):
+    f = CustomerFilter(request.GET, queryset=Customer.objects.all())
+    serializer = CustomerSerializer(f.qs, many=True)
+    export_data = ExportChange.dict_to_obj(serializer)
+    export_data = CustomerResource().export(export_data)
+    filename = utils.attachment_save(export_data)
+    BizLog.objects.addnew(request.user, BizLog.EXPORT, u"导出客户")
+    return JSONResponse({'filename': filename})
+
+
+@csrf_exempt
+@permission_required('customer.add_customer')
+def customer_save(request):
+    id = request.GET.get('id')
+    data = json.loads(request.body)
+
+    try:
+        with transaction.atomic():
+            serializer = CustomerSerializer.factory(request.user, data, id)
+            serializer.validSave()
+        return JSONResponse(serializer.data)
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'保存失败!')
+
+@csrf_exempt
+@permission_required('customer.delete_customer')
+def customer_delete(request):
+    id = request.GET.get('id')
+
+    try:
+        with transaction.atomic():
+            customer = Customer.getById(id)
+            BizLog.objects.addnew(request.user, BizLog.DELETE, u"删除客户[%s],id=%d" % (customer.name, customer.id))
+            customer.delete()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except ProtectedError:
+        return JSONError(u'该客户已被使用,禁止删除!')
+    except IntegrityError:
+        return JSONError(u'该客户已被使用,禁止删除!')
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'删除失败!')
+    return JSONResponse({})
+
+@csrf_exempt
+@permission_required('customer.import_customer')
+def customer_import(request):
+    file = request.FILES.get('excel_file')
+
+    try:
+        line = 2
+        importer = CustomerImporter()
+        excel_rows = importer.getExcelData(file)
+        with transaction.atomic():
+            for excel_row in excel_rows:
+                try:
+                    row = importer.validRow(excel_row)
+                    data = {}
+                    data['name'] = row[u'姓名']
+                    data['mobile'] = row[u'手机号']
+                    data['company_name'] = row[u'公司名称']
+                    data['company_tel'] = row[u'公司电话']
+                    data['opening_bank'] = row[u'开户行']
+                    data['account'] = row[u'账号']
+                    data['credit_code'] = row[u'信用代码']
+                    data['address'] = row[u'地址']
+                    data['notes'] = row[u'备注']
+                    serializer = CustomerSerializer.factory(request.user, data)
+                    serializer.validSave()
+                except CustomError, e:
+                    raise CustomError(u'第%d行:%s' % (line, e.get_error_msg()))
+                except Exception, e:
+                    raise CustomError(u'第%d行:%s' % (line, unicode(e)))
+                line += 1
+            BizLog.objects.addnew(request.user, BizLog.IMPORT, u"导入客户数据[%s]条" % (line-2))
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'导入失败!')
+    return JSONResponse()
+
+@csrf_exempt
+@token_required
+def customer_select(request):
+    param = request.GET.get('param') or request.GET.get('keywords')
+    rows = Customer.objects.filter()
+    if param:
+        rows = rows.filter(
+            Q(name__icontains=param)|
+            Q(mobile__icontains=param)|
+            Q(company_name__icontains=param)
+        )
+
+    serializer = CustomerSerializer(rows, many=True)
+    return JSONResponse(serializer.data)

+ 0 - 0
apps/dashboard/__init__.py


二进制
apps/dashboard/forms.pyd


+ 0 - 0
apps/dashboard/migrations/__init__.py


+ 5 - 0
apps/dashboard/models.py

@@ -0,0 +1,5 @@
+from __future__ import unicode_literals
+
+from django.db import models
+
+# Create your models here.

+ 3 - 0
apps/dashboard/tests.py

@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.

+ 10 - 0
apps/dashboard/urls.py

@@ -0,0 +1,10 @@
+#coding=utf-8
+
+from django.conf.urls import url
+
+from views import *
+
+urlpatterns = (
+
+    url(r'^home/$', home, {'template_name': 'dashboard/home.html'}),
+)

+ 57 - 0
apps/dashboard/views.py

@@ -0,0 +1,57 @@
+#coding=utf-8
+
+
+import time
+from django.contrib.auth.decorators import login_required
+from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect
+from django.shortcuts import render
+from django.contrib.auth import login as auth_login
+
+
+
+def my_login(request, template_name, authentication_form, *args, **kwargs):
+    next = request.GET.get('next')
+
+    if request.method == 'POST':
+        form = authentication_form(data=request.POST, request=request)
+        if form.is_valid():
+            user = form.get_user()
+            auth_login(request, user)
+
+            if next:
+                return HttpResponseRedirect(next)
+            else:
+                return HttpResponseRedirect('/')
+        else:
+            time.sleep(3)
+            # if request.POST['username'] != 'zzzroor':
+            #     BizLog.objects.addnew(None, BizLog.INSERT, u"[%s]登录失败,密码[%s],IP[%s]" % (
+            #         request.POST['username'],
+            #         request.POST['password'],
+            #         request.META['REMOTE_ADDR']
+            #     ))
+    else:
+        form = authentication_form()
+
+    return render(request, template_name, {'form':form, 'next':next})
+
+def my_logout(request, *args, **kwargs):
+    if not request.user.is_authenticated():
+        return HttpResponseRedirect('/')
+
+    return HttpResponseRedirect('/')
+
+
+
+
+def index(request):
+    user_id = request.META.get('HTTP_USER_ID')
+    token = request.META.get('HTTP_ACCESS_TOKEN')
+    if not user_id or not token:
+        return HttpResponseRedirect('/views/account/login.html')
+    else:
+        return HttpResponsePermanentRedirect("/views/index.html")
+
+@login_required
+def home(request, template_name):
+    return render(request, template_name)

+ 52 - 0
apps/exceptions.py

@@ -0,0 +1,52 @@
+#coding=utf-8
+
+class CustomError(Exception):
+    def __init__(self, msg, code=''):
+        """
+
+        :param code: error code
+        :param message: error message
+        :return:
+        """
+        Exception.__init__(self)
+        self.message = msg
+        self.error_code = code
+
+    def __str__(self):
+        return "%s %s" % (
+            self.error_code,
+            self.message,
+        )
+
+    def set_error_code(self, code):
+        self.error_code = code
+
+    def set_error_msg(self, msg):
+        self.message = msg
+
+    def get_error_code(self):
+        return self.error_code
+
+    def get_error_msg(self):
+        return self.message
+
+
+class ExportChange():
+
+    @staticmethod
+    def dict_to_obj(serializer):
+        export_data = []
+        for row in serializer.data:
+            item = ExportChange()
+            item.__dict__.update(row)
+            export_data.append(item)
+        return export_data
+
+    @staticmethod
+    def dict_to_obj2(data): # 不用序列化的时候用
+        export_data = []
+        for row in data:
+            item = ExportChange()
+            item.__dict__.update(row)
+            export_data.append(item)
+        return export_data

+ 0 - 0
apps/foundation/__init__.py


+ 89 - 0
apps/foundation/consts.py

@@ -0,0 +1,89 @@
+#coding=utf-8
+
+CONTENT_TYPE_SORTING = (
+    'office-officenotice',
+    'office-officenoticebrowserecord',
+
+    'plan-saleplan',  # 销售计划管理
+    'order-saleorder',  # 销售管理
+
+    'plan-productionplan',  # 生产计划管理
+
+    'purchase-purchaseplan',  # 计划管理
+    'purchase-purchaseuser',  # 询价管理
+    'purchase-purchaseorder',  # 合同管理
+    'purchase-purchasepayment',  # 付款管理
+    'purchase-purchaseorderdetail',  # 发票管理
+
+    'purchase-godownentry',  # 入库管理
+    'purchase-purchaseprice',  # 入库查询
+    'material-deliver',  # 出库管理
+    'plan-saleplandetail',  # 出库查询
+    'purchase-godownentryreturn',  # 退货管理
+    'account-subdepartment',  # 退货查询
+    'material-deliverreturn',  # 退料管理
+    'warehouse-warehouserecord',  # 退料查询
+    'warehouse-inventory',  # 盘存管理
+    'warehouse-warehousestockrecord',  # 库存查询
+
+    'purchase-godownentrydetail',  # 入库管理
+    'purchase-purchasepaymentdetail',  # 入库查询
+    'material-deliverdetail',  # 出库管理
+    'plan-productionplandetail',  # 出库查询
+    'purchase-godownentryreturndetail',  # 退货管理
+    'account-subemployee',  # 退货查询
+    'material-deliverreturndetail',  # 退料管理
+    'purchase-purchaseplandetail',  # 退料查询
+    'warehouse-inventorydetail',  # 盘存管理
+    'warehouse-warehouserecorddetail',  # 库存查询
+
+    'goods-goodsgodownentry',  # 入库管理
+    'order-saleorderdetail',  # 入库查询
+    'order-goodsdeliver',  # 出库管理
+    'order-goodsdeliverdetail',  # 出库查询
+    'order-goodsdeliverreturn',  # 退库管理
+    'order-goodsdeliverreturndetail',  # 退库查询
+    'goods-goodsgodownentrydetail',  # 盘存管理
+    'warehouse-warehousebackmap',  # 库存查询
+
+
+    'material-material',  # 原料产品管理
+    'material-consumable',  # 耗材产品管理
+    'goods-goods',  # 成品产品管理
+    'warehouse-warehouse',  # 原料仓别管理
+    'warehouse-warehouseadmin',  # 耗材仓别管理
+    'warehouse-warehousestock',  # 成品仓别管理
+    'account-department',  # 组织结构管理
+    'foundation-option',  # 自定义项管理
+    'supplier-supplier',  # 供应商管理
+    'customer-customer',  # 客户管理
+    'account-user',  # 人员管理
+    'foundation-bizlog',  # 权限管理
+    'config-config',  # 基础设置
+
+    'product-productbase'
+
+)
+
+MENU_TO_MODEL = (
+    (u'公告管理', ('office-notice', 'office-noticebrowserecord')),
+    (u'销售管理', ('order-saleorder', 'plan-saleplan')),
+    (u'生产管理', ('plan-productionplan')),
+    (u'采购管理', ('purchase-purchaseplan', 'purchase-purchaseuser', 'purchase-purchaseorder'
+               , 'purchase-purchasepayment','purchase-purchaseorderdetail',)),
+    (u'原料管理', ('purchase-godownentry', 'purchase-purchaseprice', 'material-deliver', 'plan-saleplandetail',
+               'purchase-godownentryreturn', 'account-subdepartment', 'material-deliverreturn'
+               ,'warehouse-warehouserecord','warehouse-inventory','warehouse-warehousestockrecord',)),
+    (u'耗材管理', ('purchase-godownentrydetail', 'purchase-purchasepaymentdetail', 'material-deliverdetail'
+               , 'plan-productionplandetail','purchase-godownentryreturndetail', 'account-subemployee'
+               ,'material-deliverreturndetail', 'purchase-purchaseplandetail','warehouse-inventorydetail'
+               ,'warehouse-warehouserecorddetail',)),
+    (u'成品管理', ('goods-goodsgodownentry', 'order-saleorderdetail', 'order-goodsdeliver', 'order-goodsdeliverdetail',
+               'order-goodsdeliverreturn','order-goodsdeliverreturndetail','goods-goodsgodownentrydetail'
+               ,'warehouse-warehousebackmap',)),
+    (u'基础数据', ('material-material', 'material-consumable', 'goods-goods','warehouse-warehouse'
+               , 'warehouse-warehouseadmin','warehouse-warehousestock','account-department'
+               ,'foundation-option','supplier-supplier', 'customer-customer', 'account-user','foundation-bizlog','config-config',)),
+    (u'其他',('product-productbase',))
+
+)

+ 11 - 0
apps/foundation/filters.py

@@ -0,0 +1,11 @@
+#coding=utf-8
+import django_filters
+from models import Option
+
+
+class OptionFilter(django_filters.FilterSet):
+    enabled = django_filters.CharFilter(name='enabled')
+
+    class Meta:
+        model = Option
+        fields = '__all__'

+ 16 - 0
apps/foundation/forms.py

@@ -0,0 +1,16 @@
+#coding=utf-8
+
+from django import forms
+
+from models import Option
+
+class OptionForm(forms.ModelForm):
+    class Meta:
+        fields = "__all__"
+        model = Option
+        widgets = {
+            'type': forms.Select(attrs={'class':'easyui-validatebox', 'required':'true'}),
+            'name': forms.TextInput(attrs={'size': 30, 'class':'easyui-textbox', 'required':'true'}),
+            'enabled': forms.CheckboxInput(attrs={'value': 'true'}),
+            'notes': forms.TextInput(attrs={'class':'easyui-textbox', 'multiline':'true', 'style':"width:90%;height:100px;"}),
+        }

+ 16 - 0
apps/foundation/managers.py

@@ -0,0 +1,16 @@
+#coding=utf-8
+
+from django.db import models
+
+from apps.exceptions import CustomError
+
+class OptionManager(models.Manager):
+    def _get_instance_by_type_and_id(self, type, id):
+        instance = self.model.objects.filter(type=type, id=id).first()
+        if not instance:
+            raise CustomError(
+                u'请从下拉列表中选择{0}。<br>如果{0}不在下拉列表中,请到“系统设置”-->“自定义项”添加。'.format(
+                    self.model.TYPE_CHOICES[type][1]
+                )
+            )
+        return instance

+ 0 - 0
apps/foundation/migrations/__init__.py


+ 17 - 0
apps/foundation/mixins.py

@@ -0,0 +1,17 @@
+#coding=utf-8
+
+from libs.utils import attachment_response
+from django_validator.decorators import GET
+
+class OptionExportMixin(object):
+
+    @GET('filename', type='string', default='自定义项.xls')
+    @GET('format', type='string', default='xls', validators='in: xls,xlsx')
+    @GET('empty', type='bool', default=False)
+    def get(self, request, format, filename, empty):
+        queryset = None
+        if not empty:
+            queryset = self.filter_queryset(self.get_queryset())
+        resourse = self.resource_class()
+        export_data = resourse.export(queryset, empty)
+        return attachment_response(getattr(export_data, format), filename=filename)

+ 121 - 0
apps/foundation/models.py

@@ -0,0 +1,121 @@
+#coding=utf-8
+import json
+import datetime
+
+from django.db import models
+from django.utils import timezone
+from django.conf import settings
+
+from apps.exceptions import CustomError
+from libs.utils import strftime, strfdate
+from managers import OptionManager
+
+class Option(models.Model):
+    PAYMENT_MODE = 0
+    MATERIAL_MODE = 1
+    CONSUMABLE_MODE = 2
+    GOODS_MODE = 3
+    QUALITY_REQUEST = 4
+
+    TYPE_CHOICES = (
+        (PAYMENT_MODE, u'付款方式'),
+        (MATERIAL_MODE, u'原料类别'),
+        (CONSUMABLE_MODE, u'耗材类别'),
+        (GOODS_MODE, u'成品类别'),
+        (QUALITY_REQUEST, u'质量要求'),
+    )
+
+    type = models.PositiveSmallIntegerField(choices=TYPE_CHOICES, verbose_name=u"类别")
+    name = models.CharField(max_length=100, verbose_name=u"名称")
+    notes = models.CharField(max_length=500, verbose_name=u"备注",blank=True,null=True)
+    enabled = models.BooleanField(verbose_name=u"在用", default=True)
+
+    objects = OptionManager()
+
+    @staticmethod
+    def getTypeText(type):
+        return Option.TYPE_CHOICES[type][1]
+
+    @staticmethod
+    def getById(id):
+        try:
+            id = int(id)
+        except:
+            raise CustomError(u'无效的自定义项ID')
+
+        instance = Option.objects.filter(pk=id).first()
+        if not instance:
+            raise CustomError(u'未找到相应的自定义项')
+        return instance
+
+    @staticmethod
+    def getByName(name, type):
+        option = Option.objects.filter(name=name,type=type).first()
+        if not option:
+            raise CustomError(u'未找到[%s]为[%s]的自定义项' % (Option.getTypeText(type),name))
+        return option
+
+    class Meta:
+        db_table = "system_option"
+        verbose_name = u"自定义项管理"
+        ordering = ('-id', )
+        index_together = (
+            'name',
+        )
+        default_permissions = ()
+        permissions = (
+            ("view_option", u"浏览"),
+            ("add_option", u"添加"),
+            ("delete_option", u"删除"),
+        )
+
+class BizLogManager(models.Manager):
+    def addnew(self, user, type, description, data=None):
+        def default(o):
+            if isinstance(o, datetime.datetime):
+                return strftime(o)
+            elif isinstance(o, datetime.date):
+                return strfdate(o)
+
+        row = self.model(user=user, type=type, description=description, create_time=timezone.now())
+        if data:
+            row.data = json.dumps(data, default=default)
+        row.save()
+        return row
+
+class BizLog(models.Model):
+    INSERT = 1
+    UPDATE = 2
+    DELETE = 3
+    CHECK = 4
+    EXPORT = 5
+    PRINT = 6
+    IMPORT = 7
+    TYPE_CHOICES = (
+        (INSERT, u'添加'),
+        (UPDATE, u'修改'),
+        (DELETE, u'删除'),
+        (CHECK, u'审核'),
+        (EXPORT, u'导出'),
+        (PRINT, u'打印'),
+        (IMPORT, u'导入'),
+    )
+
+    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, null=True, blank=True)
+    type = models.PositiveSmallIntegerField(choices=TYPE_CHOICES, verbose_name=u"类别")
+    description = models.CharField(max_length=1000, verbose_name=u"内容")
+    data = models.TextField(verbose_name=u"数据", null=True, blank=True)
+    create_time = models.DateTimeField(verbose_name=u"添加时间", auto_now_add=True, editable=False)
+
+    objects = BizLogManager()
+
+    class Meta:
+        db_table = "system_log"
+        ordering = ['-id']
+        verbose_name = u"权限管理"
+        default_permissions = ()
+        permissions = (
+            ("view_group", u"浏览"),
+            ("add_group", u"添加"),
+            ("delete_group", u"删除"),
+        )

+ 22 - 0
apps/foundation/resources.py

@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+from import_export.fields import Field
+from import_export import resources
+from apps.foundation.models import Option
+
+class OptionResource(resources.ModelResource):
+
+    def __init__(self):
+        super(OptionResource, self).__init__()
+        self.fields['name'] = Field(attribute='name')
+        self.fields['type_text'] = Field(attribute='type_text')
+        self.fields['enabled_text'] = Field(attribute='enabled_text')
+        self.fields['notes'] = Field(attribute='notes')
+
+    def get_export_headers(self):
+        return [u'名称', u'类别', u'在用', u'备注',]
+
+    class Meta:
+        model = Option
+        fields = ('name', 'type_text', 'enabled_text', 'notes',)
+        export_order = fields

+ 67 - 0
apps/foundation/serializers.py

@@ -0,0 +1,67 @@
+#coding=utf-8
+
+from rest_framework import serializers
+from django.db.models import Q
+
+from apps.serializer_errors import dump_serializer_errors
+from libs.booleancharfield import BooleanCharField
+from apps.exceptions import CustomError
+
+from apps.foundation.models import BizLog
+from models import Option
+
+class OptionSerializer(serializers.ModelSerializer):
+    enabled_text = BooleanCharField(source='enabled', read_only=True)
+    type_text = serializers.CharField(source='get_type_display', read_only=True)
+
+    class Meta:
+        model = Option
+        fields = '__all__'
+        #fields = ('id', 'name', 'notes', 'enabled')
+
+    @staticmethod
+    def factory(user, data,id=None):
+        if id:
+            instance = Option.getById(id)
+        else:
+            instance = None
+
+        serializer = OptionSerializer(instance, data=data)
+        serializer.user = user
+        return serializer
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+    def create(self, validated_data):
+        item = Option.objects.filter(type=validated_data['type'], name=validated_data['name']).first()
+        if item:
+            raise CustomError(u'类型为[%s]的自定义项[%s]已存在' % (Option.getTypeText(validated_data['type']), validated_data['name']))
+
+        instance = super(OptionSerializer, self).create(validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加自定义项[%s],id=%d" % (instance.name, instance.id),
+            validated_data
+        )
+        return instance
+
+    def update(self, instance, validated_data):
+        if validated_data['type'] != instance.type:
+            raise CustomError(u'禁止修改类别')
+        item = Option.objects.filter(Q(type=instance.type), Q(name=validated_data['name']), ~Q(id=instance.id)).first()
+        if item:
+            raise CustomError(u'类型为[%s]的自定义项[%s]已存在' % (Option.getTypeText(validated_data['type']), validated_data['name']))
+
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.UPDATE,
+            u"修改自定义项[%s],id=%s" % (validated_data['name'], id),
+            validated_data
+        )
+        instance = super(OptionSerializer, self).update(instance, validated_data)
+        return instance

+ 17 - 0
apps/foundation/urls.py

@@ -0,0 +1,17 @@
+#coding=utf-8
+
+from django.conf.urls import url
+
+from views import *
+
+urlpatterns = (
+    url(r'^option/data/$', option_list),
+    url(r'^option/save/$', option_save),
+    url(r'^option/delete/$', option_delete),
+    url(r'^option/types/$', option_types),
+    url(r'^search_options/$', search_options),
+
+    url(r'^log/data/$', log_list),
+
+    url(r'^mobile/license/$', mobile_license),
+)

+ 107 - 0
apps/foundation/views.py

@@ -0,0 +1,107 @@
+# coding=utf-8
+
+import traceback
+import json
+
+from django.views.decorators.csrf import csrf_exempt
+from django.db import transaction,IntegrityError
+from django.db.models import ProtectedError
+
+from apps.foundation.filters import OptionFilter
+from libs.http import JSONResponse, JSONError, DataGridJSONResponse
+from libs import utils
+from apps.exceptions import CustomError
+from apps.account.decorators import token_required,permission_required
+
+from models import Option
+from serializers import OptionSerializer
+from resources import OptionResource
+from apps.dashboard.forms import fetch_license
+from django.core.cache import cache
+
+@csrf_exempt
+@permission_required('foundation.view_option')
+def option_list(request):
+    f = OptionFilter(request.GET, queryset=Option.objects.all())
+    rows, total = utils.get_page_data(request, f.qs)
+    serializer = OptionSerializer(rows, many=True)
+
+    return DataGridJSONResponse(serializer.data, total)
+
+@csrf_exempt
+@permission_required('foundation.add_option')
+def option_save(request):
+    id = request.GET.get('id')
+    data = json.loads(request.body)
+    try:
+        with transaction.atomic():
+            serializer = OptionSerializer.factory(request.user,data,id)
+            serializer.validSave()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'保存失败!')
+    return JSONResponse()
+
+@permission_required('foundation.delete_option')
+def option_delete(request):
+    id = request.GET.get('id')
+
+    try:
+        with transaction.atomic():
+            option = Option.getById(id)
+            option.delete()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except ProtectedError:
+        return JSONError(u'该自定义项已被使用,禁止删除!')
+    except IntegrityError:
+        return JSONError(u'该自定义项已被使用,禁止删除!')
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'删除失败!')
+    return JSONResponse({})
+
+@csrf_exempt
+@token_required
+def option_types(request):
+    data = []
+    for row in Option.TYPE_CHOICES:
+        data.append(
+            {'id': row[0], 'value': row[1]}
+        )
+
+    return JSONResponse(data)
+
+@csrf_exempt
+@token_required
+def log_list(request):
+    pass
+
+def mobile_license(request):
+    try:
+        fetch_license()
+    except Exception, e:
+        return JSONError(e.message)
+
+    license = cache.get('license')
+    result = {
+        'allow_app':license['allow_app'],
+    }
+    return JSONResponse(result)
+
+@csrf_exempt
+@token_required
+def search_options(request):
+    type = int(request.GET.get('type'))
+    rows = Option.objects.filter(type=type, enabled=True)
+    data = []
+    for row in rows:
+        item = {
+            'id': row.id,
+            'name': row.name
+        }
+        data.append(item)
+
+    return JSONResponse(data)

+ 0 - 0
apps/goods/__init__.py


+ 55 - 0
apps/goods/filters.py

@@ -0,0 +1,55 @@
+#coding=utf-8
+
+import django_filters
+from apps.base import clean_datetime_range
+from models import Goods, GoodsGodownEntry
+from apps.product.models import ProductBase
+
+
+class GoodsFilter(django_filters.FilterSet):
+    name = django_filters.CharFilter(name='product_base__name',lookup_expr='icontains')
+    model = django_filters.CharFilter(name='product_base__model', lookup_expr='icontains')
+    enabled = django_filters.CharFilter(name='product_base__enabled')
+
+    class Meta:
+        model = Goods
+        fields = (
+            'name', 'model', 'enabled'
+        )
+
+    def __init__(self, data=None, *args, **kwargs):
+        data = clean_datetime_range(data, 'create_time')
+        super(GoodsFilter, self).__init__(data, *args, **kwargs)
+
+
+class GoodsGodownEntryFilter(django_filters.FilterSet):
+    no = django_filters.CharFilter(name='no', lookup_expr='icontains')
+    create_user_text = django_filters.CharFilter(name='create_user__name', lookup_expr='icontains')
+    status = django_filters.CharFilter(name='status')
+    create_time = django_filters.DateTimeFromToRangeFilter(field_name='create_time')
+    check_time = django_filters.DateTimeFromToRangeFilter(field_name='check_time')
+    class Meta:
+        model = GoodsGodownEntry
+        fields = "__all__"
+
+    def __init__(self, data=None, *args, **kwargs):
+        data = clean_datetime_range(data, 'create_time', 'source')
+        data = clean_datetime_range(data, 'check_time')
+        super(GoodsGodownEntryFilter, self).__init__(data, *args, **kwargs)
+
+
+class GoodsProductFilter(django_filters.FilterSet):
+    name = django_filters.CharFilter(name='name', lookup_expr='icontains')
+    model = django_filters.CharFilter(name='model', lookup_expr='icontains')
+    code = django_filters.CharFilter(name='code', lookup_expr='icontains')
+    enabled = django_filters.CharFilter(name='product_base__enabled')
+
+    class Meta:
+        model = ProductBase
+        fields = (
+            'name', 'model', 'code', 'enabled'
+        )
+
+    def __init__(self, data=None, *args, **kwargs):
+        data = clean_datetime_range(data, 'create_time')
+        super(GoodsProductFilter, self).__init__(data, *args, **kwargs)

+ 0 - 0
apps/goods/migrations/__init__.py


+ 157 - 0
apps/goods/models.py

@@ -0,0 +1,157 @@
+#coding=utf-8
+from django.db import models
+from django.db.models import Sum
+from django.utils import timezone
+
+from apps.account.models import User, Department
+from apps.exceptions import CustomError
+from apps.product.models import ProductBase
+from apps.warehouse.models import Warehouse, WarehouseStock, WarehouseStockRecord
+from django.conf import settings
+
+class Goods(models.Model):
+    product_base = models.ForeignKey(ProductBase, verbose_name=u"产品", on_delete=models.PROTECT, blank=True)
+    retail_price = models.BigIntegerField(verbose_name=u"销售价", help_text=u"元", default=0)
+    standard = models.CharField(max_length=100, verbose_name=u"标准", null=True, blank=True)
+    goods_pack = models.CharField(max_length=100, verbose_name=u"包装", null=True, blank=True)
+
+    class Meta:
+        db_table = "goods"
+        verbose_name = u"成品产品管理"
+        ordering = ('-id',)
+        default_permissions = ()
+        permissions = (
+            ("view_goods", u"浏览"),
+            ("add_goods", u"添加"),
+            ("delete_goods", u"删除"),
+            ("export_goods", u"导出"),
+            ("import_goods", u"导入"),
+            ("view_goods_cost", u"查看成本"),
+        )
+
+    @staticmethod
+    def getById(id):
+        instance = Goods.objects.filter(pk=id).first()
+        if not instance:
+            raise CustomError(u'未找到相应的成品')
+        return instance
+
+    @staticmethod
+    def getByBaseId(base_id):
+        instance = Goods.objects.filter(product_base_id=base_id).first()
+        if not instance:
+            raise CustomError(u'未找到相应的成品')
+        return instance
+
+class GoodsGodownEntry(models.Model):
+    no = models.CharField(max_length=20, verbose_name=u"入库单号", editable=False)
+    warehouse = models.ForeignKey(Warehouse, verbose_name=u'仓别', on_delete=models.PROTECT)
+    create_time = models.DateTimeField(verbose_name=u"创建时间", default=timezone.now)
+    create_user = models.ForeignKey(User, related_name='goods_godown_entry_ref_create_user', verbose_name=u"创建人", on_delete=models.PROTECT, editable=False)
+    department = models.ForeignKey(Department, verbose_name=u"创建部门", on_delete=models.PROTECT, editable=False)
+    check_time = models.DateTimeField(verbose_name=u"审核时间", blank=True, null=True)
+    check_user = models.ForeignKey(User, related_name='goods_godown_entry_ref_check_user', verbose_name=u"审核人", on_delete=models.PROTECT, blank=True, null=True)
+    status = models.PositiveSmallIntegerField(choices=settings.CHECK_STATUS_CHOICES, default=settings.DEFAULT, verbose_name=u"审核状态")
+    notes = models.CharField(max_length=200, verbose_name=u"备注", blank=True, null=True)
+
+    total_count = models.BigIntegerField(verbose_name=u'合计数量', default=0)
+    total_amount = models.BigIntegerField(verbose_name=u'合计金额', default=0)
+    total_invoice_amount = models.BigIntegerField(verbose_name=u'发票金额合计', default=0)  # 等于明细里面的发票金额合计
+    total_deliver_count = models.BigIntegerField(verbose_name=u"出库数量合计", default=0)
+    total_deliver_amount = models.BigIntegerField(verbose_name=u"出库金额合计", default=0)
+
+    class Meta:
+        db_table = "goods_godown_entry"
+        verbose_name = u"入库管理"
+        ordering = ('-id',)
+        index_together = (
+            'create_time', 'check_time', 'status'
+        )
+        unique_together = (
+            'no',
+        )
+        default_permissions = ()
+        permissions = (
+            ("view_goods_godown_entry", u"浏览"),
+            ("add_goods_godown_entry", u"添加"),
+            ("check_goods_godown_entry", u"审核"),
+            ("delete_goods_godown_entry", u"删除"),
+            ("export_goods_godown_entry", u"导出"),
+            ("import_goods_godown_entry", u"导入"),
+            ("print_goods_godown_entry", u"打印"),
+            ("edit_goods_godown_entry", u"高级修改"),
+        )
+
+    @staticmethod
+    def getById(id):
+        instance = GoodsGodownEntry.objects.filter(pk=id).first()
+        if not instance:
+            raise CustomError(u'未找到相应的成品入库单')
+        return instance
+
+    def save(self, *args, **kwargs):
+        if self.no == None or self.no == '':
+            now = timezone.now()
+            rows = GoodsGodownEntry.objects.filter(create_time__gte=now.strftime('%Y-%m-%d')).order_by('-no')
+            count = rows.count()
+            if count == 0:
+                self.no = 'GR%s%03d' % (now.strftime('%Y%m%d'), count + 1)
+            else:
+                self.no = rows[0].no[:2] + str(int(rows[0].no[2:]) + 1)
+        super(GoodsGodownEntry, self).save(*args, **kwargs)
+
+    def update_total(self):
+        total_count = 0
+        total_amount = 0
+        total_invoice_amount = 0
+        sum_row = GoodsGodownEntryDetail.objects.filter(main=self).aggregate(total_count=Sum('count'),
+                                                total_amount=Sum('amount'), total_invoice_amount=Sum('invoice_amount'))
+        if sum_row:
+            total_count = sum_row['total_count'] or 0
+            total_amount = sum_row['total_amount'] or 0
+            total_invoice_amount = sum_row['total_invoice_amount'] or 0
+        self.total_count = total_count
+        self.total_amount = total_amount
+        self.total_invoice_amount = total_invoice_amount
+        self.save()
+
+    def update_redundant(self):
+        sum_count = 0
+        sum_amount = 0
+        sum_row = GoodsGodownEntryDetail.objects.filter(main=self).aggregate(sum_count=Sum('deliver_count'),
+                                                                             sum_amount=Sum('deliver_amount'))
+        if sum_row:
+            sum_count = sum_row['sum_count'] or 0
+            sum_amount = sum_row['sum_amount'] or 0
+        self.total_deliver_count = sum_count
+        self.total_deliver_amount = sum_amount
+        self.save()
+
+
+class GoodsGodownEntryDetail(models.Model):
+    main = models.ForeignKey(GoodsGodownEntry, related_name='goods_godown_entry_detail_ref_main', verbose_name=u"成品入库单", on_delete=models.PROTECT)
+    goods = models.ForeignKey(Goods, verbose_name=u"成品", on_delete=models.PROTECT)
+    count = models.BigIntegerField(verbose_name=u"数量", default=0)
+    price = models.BigIntegerField(verbose_name=u'单价', default=0)
+    warehouse_stock = models.ForeignKey(WarehouseStock, verbose_name=u'仓别库存', on_delete=models.PROTECT, editable=False)
+    stock_record = models.ForeignKey(WarehouseStockRecord, verbose_name=u'库存记录', on_delete=models.PROTECT, null=True, blank=True, related_name='goods_godown_entry_detail_ref_stock_record')
+
+    amount = models.BigIntegerField(verbose_name=u'金额', default=0)
+    invoice_amount = models.BigIntegerField(verbose_name=u'发票金额', default=0) #手工录入
+    deliver_count = models.BigIntegerField(verbose_name=u"出库数量", default=0)
+    deliver_amount = models.BigIntegerField(verbose_name=u"出库金额", default=0)
+    notes = models.CharField(max_length=200, verbose_name=u"备注", blank=True, null=True)
+
+    class Meta:
+        db_table = "goods_godown_entry_detail"
+        verbose_name = u"盘存管理"
+        ordering = ('-id',)
+        default_permissions = ()
+        permissions = ( # 成品入库明细
+            ("view_goods_inventory", u"浏览"),
+            ("add_goods_inventory", u"添加"),
+            ("check_goods_inventory", u"审核"),
+            ("delete_goods_inventory", u"删除"),
+            ("export_goods_inventory", u"导出"),
+            ("print_goods_inventory", u"打印"),
+        )

+ 174 - 0
apps/goods/resources.py

@@ -0,0 +1,174 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+from import_export import resources
+from import_export.fields import Field
+from apps.base import ExcelImporter
+from apps.goods.models import Goods, GoodsGodownEntry, GoodsGodownEntryDetail
+
+
+class GoodsResource(resources.Resource):
+
+    def __init__(self):
+        super(GoodsResource, self).__init__()
+        self.fields['name'] = Field(attribute='name')
+        self.fields['model'] = Field(attribute='model')
+        self.fields['option_type_text'] = Field(attribute='option_type_text')
+        self.fields['standard'] = Field(attribute='standard')
+        self.fields['goods_pack'] = Field(attribute='goods_pack')
+        self.fields['unit'] = Field(attribute='unit')
+        self.fields['code'] = Field(attribute='code')
+        self.fields['warehouse_place'] = Field(attribute='warehouse_place')
+        self.fields['retail_price'] = Field(attribute='retail_price')
+        self.fields['enabled_text'] = Field(attribute='enabled_text')
+        self.fields['notes'] = Field(attribute='notes')
+
+    def get_export_headers(self):
+        return [u'名称', u'代码', u'类别', u'标准', u'包装', u'单位', u'助记码', u'库位', u'销售价', u'在用', u'备注',]
+
+    class Meta:
+        fields = ('name', 'model', 'option_type_text', 'standard', 'goods_pack', 'unit', 'code', 'warehouse_place', 'retail_price',
+                  'enabled_text', 'notes',)
+        export_order = fields
+
+
+class GoodsImporter(ExcelImporter):
+    fields = {
+        u'类别': (True, ExcelImporter.formatUnicode),
+        u'名称': (True, ExcelImporter.formatUnicode),
+        u'代码': (True, ExcelImporter.formatUnicode),
+        u'标准': (False, ExcelImporter.formatUnicode),
+        u'包装': (False, ExcelImporter.formatUnicode),
+        u'库位': (False, ExcelImporter.formatUnicode),
+        u'销售价': (True, ExcelImporter.formatFloatGeZ),
+        u'备注': (False, ExcelImporter.formatUnicode),
+    }
+
+class GoodsGodownEntryResource(resources.Resource):
+
+    def __init__(self, is_show_cost=True):
+        super(GoodsGodownEntryResource, self).__init__()
+        self.is_show_cost = is_show_cost
+        self.fields['no'] = Field(attribute='no')
+        self.fields['total_count'] = Field(attribute='total_count')
+        self.fields['warehouse_text'] = Field(attribute='warehouse_text')
+        self.fields['create_user_text'] = Field(attribute='create_user_text')
+        self.fields['create_time'] = Field(attribute='create_time')
+        self.fields['status_text'] = Field(attribute='status_text')
+        self.fields['check_user_text'] = Field(attribute='check_user_text')
+        self.fields['check_time'] = Field(attribute='check_time')
+        self.fields['notes'] = Field(attribute='notes')
+        if is_show_cost:
+            self.fields['total_amount'] = Field(attribute='total_amount')
+            self.fields['total_invoice_amount'] = Field(attribute='total_invoice_amount')
+            fields = ('no', 'total_count', 'total_amount','total_invoice_amount', 'warehouse_text', 'create_user_text', 'create_time', 'status_text',
+            'check_user_text', 'check_time', 'notes')
+        else:
+            fields = ('no', 'total_count', 'warehouse_text', 'create_user_text', 'create_time', 'status_text',
+                      'check_user_text', 'check_time', 'notes')
+        self._meta.export_order = fields
+
+    def get_export_headers(self):
+        if self.is_show_cost:
+            return [u'单号', u'数量合计', u'金额合计',u'发票金额合计', u'仓别', u'创建人', u'创建时间' , u'审核状态',u'审核人', u'审核时间', u'备注']
+        return [u'单号', u'数量合计', u'仓别', u'创建人', u'创建时间' , u'审核状态',u'审核人', u'审核时间', u'备注']
+
+class GoodsGodownEntryDetailResource(resources.Resource):
+
+    def __init__(self, is_show_cost=True):
+        super(GoodsGodownEntryDetailResource, self).__init__()
+        self.is_show_cost = is_show_cost
+        self.fields['goods_text'] = Field(attribute='goods_text')
+        self.fields['goods_model'] = Field(attribute='goods_model')
+        self.fields['count'] = Field(attribute='count')
+        self.fields['notes'] = Field(attribute='notes')
+        if is_show_cost:
+            self.fields['price'] = Field(attribute='price')
+            self.fields['amount'] = Field(attribute='amount')
+            self.fields['invoice_amount'] = Field(attribute='invoice_amount')
+            fields = ('goods_text', 'goods_model', 'count', 'price', 'amount', 'invoice_amount', 'notes')
+        else:
+            fields = ('goods_text', 'goods_model', 'count', 'notes')
+
+        self._meta.export_order = fields
+
+    def get_export_headers(self):
+        if self.is_show_cost:
+            return [u'成品名称', u'成品代码', u'数量', u'单价', u'金额', u'发票金额', u'备注']
+        return [u'成品名称', u'成品代码', u'数量', u'备注']
+
+
+class GoodsGodownEntryImporter(ExcelImporter):
+
+    fields = {
+        u'成品名称': (False, ExcelImporter.formatUnicode),
+        u'成品代码': (True, ExcelImporter.formatUnicode),
+        u'数量': (True, ExcelImporter.formatFloatGtZ),
+        u'单价': (True, ExcelImporter.formatFloatGeZ),
+        u'发票金额': (True, ExcelImporter.formatFloatGeZ),
+        u'备注': (False, ExcelImporter.formatUnicode),
+    }
+
+
+class GoodsProductResource(resources.Resource):
+
+    def __init__(self):
+        super(GoodsProductResource, self).__init__()
+        self.fields['name'] = Field(attribute='name')
+        self.fields['model'] = Field(attribute='model')
+        self.fields['option_type_text'] = Field(attribute='option_type_text')
+        self.fields['standard'] = Field(attribute='standard')
+        self.fields['stock_count'] = Field(attribute='stock_count')
+        self.fields['warehouse_place'] = Field(attribute='warehouse_place')
+        self.fields['unit'] = Field(attribute='unit')
+        self.fields['enabled_text'] = Field(attribute='enabled_text')
+        self.fields['code'] = Field(attribute='code')
+        self.fields['notes'] = Field(attribute='notes')
+
+    def get_export_headers(self):
+        return [u'名称', u'代码', u'类别', u'规格', u'库存', u'库位', u'单位', u'在用',  u'助记码', u'备注',]
+
+    class Meta:
+        fields = ('name', 'model', 'option_type_text', 'standard', 'stock_count', 'warehouse_place', 'unit',
+                  'enabled_text', 'code', 'notes',)
+        export_order = fields
+
+class GodownEntryQueryResource(resources.Resource):
+
+    def __init__(self, is_show_cost=True):
+        super(GodownEntryQueryResource, self).__init__()
+        self.is_show_cost = is_show_cost
+        self.fields['no'] = Field(attribute='no')
+        self.fields['type'] = Field(attribute='type')
+        self.fields['product_name'] = Field(attribute='product_name')
+        self.fields['product_model'] = Field(attribute='product_model')
+        self.fields['product_unit'] = Field(attribute='product_unit')
+        self.fields['product_type'] = Field(attribute='product_type')
+        self.fields['warehouse'] = Field(attribute='warehouse')
+        self.fields['count'] = Field(attribute='count')
+        self.fields['deliver_count'] = Field(attribute='deliver_count')
+        self.fields['surplus_count'] = Field(attribute='surplus_count')
+        self.fields['create_user'] = Field(attribute='create_user')
+        self.fields['check_time'] = Field(attribute='check_time')
+        self.fields['warehouse_place'] = Field(attribute='warehouse_place')
+        self.fields['notes'] = Field(attribute='notes')
+        if is_show_cost:
+            self.fields['price'] = Field(attribute='price')
+            self.fields['amount'] = Field(attribute='amount')
+            fields = (
+            'no', 'type', 'product_name', 'product_model', 'product_unit', 'product_type', 'warehouse',
+            'price', 'count', 'amount', 'deliver_count', 'surplus_count', 'create_user', 'check_time',
+            'warehouse_place', 'notes')
+        else:
+            fields = (
+            'no', 'type', 'product_name', 'product_model', 'product_unit', 'product_type', 'warehouse',
+            'count', 'deliver_count', 'surplus_count', 'create_user', 'check_time',
+            'warehouse_place', 'notes')
+        self._meta.export_order = fields
+
+    def get_export_headers(self):
+        if self.is_show_cost:
+            return [u'入库单号', u'入库类别', u'成品名称', u'成品代码', u'单位', u'产品类别', u'仓别', u'入库单价', u'数量'
+                , u'合计金额', u'出库数量', u'剩余数量', u'创建人', u'审核时间', u'存放库位', u'备注']
+
+        return [u'入库单号', u'入库类别', u'成品名称', u'成品代码', u'单位', u'产品类别', u'仓别', u'数量',
+                u'出库数量', u'剩余数量', u'创建人', u'审核时间', u'存放库位', u'备注']

+ 188 - 0
apps/goods/serializers.py

@@ -0,0 +1,188 @@
+#coding=utf-8
+
+from rest_framework import serializers
+
+from apps import base
+from apps.base import Formater
+from apps.exceptions import CustomError
+from apps.foundation.models import BizLog
+from apps.serializer_errors import dump_serializer_errors
+from models import Goods, GoodsGodownEntry, GoodsGodownEntryDetail
+from libs.booleancharfield import BooleanCharField, PriceShowCharField, CountShowCharField, AmountShowCharField
+from apps.product.models import ProductBase
+from apps.warehouse.models import WarehouseStock
+
+
+class GoodsSerializer(serializers.ModelSerializer):
+    name = serializers.CharField(source='product_base.name', read_only=True)
+    model = serializers.CharField(source='product_base.model', read_only=True)
+    option_type = serializers.CharField(source='product_base.option_type.id', read_only=True)
+    option_type_text = serializers.CharField(source='product_base.option_type.name', read_only=True)
+    warehouse_place = serializers.CharField(source='product_base.warehouse_place', allow_blank=True, read_only=True)
+    retail_price = PriceShowCharField()
+    enabled_text = BooleanCharField(source='product_base.enabled', read_only=True)
+    enabled = serializers.BooleanField(source='product_base.enabled', read_only=True)
+    notes = serializers.CharField(source='product_base.notes', allow_blank=True, read_only=True)
+    unit = serializers.CharField(source='product_base.unit', read_only=True)
+    code = serializers.CharField(source='product_base.code', read_only=True)
+    class Meta:
+        model = Goods
+        fields = '__all__'
+
+    @staticmethod
+    def factory(user, data, id=None):
+        if id:
+            instance = Goods.getById(id)
+        else:
+            instance = None
+        serializer = GoodsSerializer(instance, data=data)
+        serializer.user = user
+        return serializer
+
+    def create(self, validated_data):
+        product = Goods.objects.create(**validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加成品[%s],id=%d" % (product.product_base.name, product.id),
+            validated_data
+        )
+        return product
+
+    def update(self, instance, validated_data):
+        instance = super(GoodsSerializer, self).update(instance, validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.UPDATE,
+            u"修改成品[%s],id=%d" % (instance.product_base.name, instance.id),
+            validated_data
+        )
+        return instance
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+    def validate(self, data):
+        if 'retail_price' in data:
+            data['retail_price'] = Formater.formatPrice(data['retail_price'])
+        return data
+
+
+class GoodsGodownEntrySerializer(serializers.ModelSerializer):
+    status = serializers.CharField(read_only=True)
+    no = serializers.CharField(read_only=True)
+    status_text = serializers.CharField(source='get_status_display', read_only=True)
+    create_user_text = serializers.CharField(source='create_user.name', read_only=True)
+    department_text = serializers.CharField(source='department.name', read_only=True)
+    total_count = CountShowCharField(read_only=True)
+    total_amount = AmountShowCharField(read_only=True)
+    total_invoice_amount = AmountShowCharField(read_only=True)
+    check_user_text = serializers.CharField(source='check_user.name', read_only=True)
+    check_time = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    create_time = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    warehouse_text = serializers.CharField(source='warehouse.name', read_only=True)
+
+    class Meta:
+        model = GoodsGodownEntry
+        fields = '__all__'
+
+    @staticmethod
+    def factory(user, data, id=None):
+        if id:
+            instance = GoodsGodownEntry.getById(id)
+        else:
+            instance = None
+        serializer = GoodsGodownEntrySerializer(instance, data=data)
+        serializer.user = user
+        return serializer
+
+    def create(self, validated_data):
+        validated_data['create_user'] = self.user
+        validated_data['department'] = self.user.department
+        instance = GoodsGodownEntry.objects.create(**validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加成品入库单[%s],id=%d" % (instance.no, instance.id),
+            validated_data
+        )
+        return instance
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+    def update(self, instance, validated_data):
+        instance = super(GoodsGodownEntrySerializer, self).update(instance, validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.UPDATE,
+            u"修改成品入库单[%s],id=%d" % (instance.no, instance.id),
+            validated_data
+        )
+        return instance
+
+class GoodsGodownEntryDetailSerializer(serializers.ModelSerializer):
+    goods_text = serializers.CharField(source='goods.product_base.name', read_only=True)
+    goods_model = serializers.CharField(source='goods.product_base.model', read_only=True)
+    count = CountShowCharField()
+    price = PriceShowCharField()
+    amount = AmountShowCharField(read_only=True)
+    invoice_amount = AmountShowCharField()
+
+    class Meta:
+        model = GoodsGodownEntryDetail
+        fields = '__all__'
+
+    @staticmethod
+    def factory(user, data):
+        serializer = GoodsGodownEntryDetailSerializer(None, data=data)
+        serializer.user = user
+        return serializer
+
+    def create(self, validated_data):
+        instance = GoodsGodownEntryDetail.objects.create(**validated_data)
+
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加成品入库明细[%s],id=%d" % (instance.goods.product_base.name, instance.id),
+            validated_data
+        )
+        return instance
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+    def validate(self, data):
+        data['price'] = Formater.formatPrice(data['price'])
+        data['count'] = Formater.formatCount(data['count'])
+        data['amount'] = data['count'] * data['price']
+        data['invoice_amount'] = Formater.formatAmount(data['invoice_amount'])
+
+        warehouse = data['main'].warehouse
+        product_base = data['goods'].product_base
+        warehouse_stock = WarehouseStock.getByWarehouseAndProduct(warehouse, product_base)
+        data['warehouse_stock'] = warehouse_stock
+
+        return data
+
+
+class GoodsProductSerializer(serializers.ModelSerializer):
+    option_type_text = serializers.CharField(source='option_type.name', read_only=True)
+    stock_count = CountShowCharField()
+    enabled_text = BooleanCharField(source='enabled', read_only=True)
+
+    class Meta:
+        model = ProductBase
+        fields = '__all__'
+
+

+ 34 - 0
apps/goods/urls.py

@@ -0,0 +1,34 @@
+#coding=utf-8
+
+from django.conf.urls import url
+
+from views import *
+
+urlpatterns = (
+    url(r'^data/$', goods_list),
+    url(r'^export/$', goods_export),
+    url(r'^save/$', goods_save),
+    url(r'^delete/$', goods_delete),
+    url(r'^import/$', goods_import),
+    url(r'^select/$', goods_select),
+
+
+    url(r'^stock_log/$', goods_stock_log),
+    url(r'^product/data/$', goods_product_list),
+    url(r'^product/export/$', goods_product_export),
+
+    url(r'^godownentry/data/$', godownentry_list),
+    url(r'^godownentry/save/$', godownentry_save),
+    url(r'^godownentry/detail/$', godownentry_detail),
+    url(r'^godownentry/delete/$', godownentry_delete),
+    url(r'^godownentry/import/$', godownentry_import),
+    url(r'^godownentry/export/$', godownentry_export),
+    url(r'^godownentry/check/$', godownentry_check),
+    url(r'^godownentry/senior_edit/$', godownentry_senior_edit),
+    url(r'^godownentry/export_detail/$', godownentry_export_detail),
+
+    url(r'^godownentry_query/data/$', godownentry_query_list),
+    url(r'^godownentry_query/export/$', godownentry_query_export),
+    url(r'^godownentry_query/detail/$', godownentry_query_detail),
+
+)

+ 1059 - 0
apps/goods/views.py

@@ -0,0 +1,1059 @@
+# coding=utf-8
+
+import traceback
+import json
+from django.db import models,connection
+from libs.utils import strftime, strfdate
+
+from django.conf import settings
+from django.utils import timezone
+
+from apps.base import Formater
+from django.db.models import Q, Sum, F
+from django.views.decorators.csrf import csrf_exempt
+from django.db import transaction,IntegrityError
+from django.db.models import ProtectedError
+from apps.account.decorators import token_required, permission_required, isHasPermissions
+from apps.foundation.models import BizLog, Option
+from apps.goods.filters import GoodsFilter, GoodsGodownEntryFilter, GoodsProductFilter
+from apps.goods.models import Goods, GoodsGodownEntry, GoodsGodownEntryDetail
+from apps.goods.resources import GoodsResource, GoodsImporter, GoodsGodownEntryResource, GoodsGodownEntryDetailResource, \
+    GoodsGodownEntryImporter, GoodsProductResource, GodownEntryQueryResource
+from apps.goods.serializers import GoodsSerializer, GoodsGodownEntrySerializer, GoodsGodownEntryDetailSerializer, GoodsProductSerializer
+from apps.product.models import ProductBase
+from apps.product.serializers import ProductBaseSerializer
+from apps.warehouse.biz import BizWarehouse, GetWarehouseSrockRecord
+from apps.warehouse.models import WarehouseStock, Warehouse, WarehouseRecord, WarehouseStockRecord, InventoryDetail, WarehouseRecordDetail
+from apps.order.models import GoodsDeliver, GoodsDeliverReturnDetail, GoodsDeliverDetail, GoodsDeliverReturn
+
+from libs.http import JSONResponse, JSONError, DataGridJSONResponse
+from libs import utils
+from apps.exceptions import CustomError, ExportChange
+
+
+@permission_required('goods.view_goods')
+def goods_list(request):
+    f = GoodsFilter(request.GET, queryset=Goods.objects.filter())
+    rows, total = utils.get_page_data(request, f.qs)
+    serializer = GoodsSerializer(rows, many=True)
+    return DataGridJSONResponse(serializer.data, total)
+
+
+@permission_required('goods.export_goods')
+def goods_export(request):
+    f = GoodsFilter(request.GET, queryset=Goods.objects.filter())
+    serializer = GoodsSerializer(f.qs, many=True)
+    export_data = ExportChange.dict_to_obj(serializer)
+    export_data = GoodsResource().export(export_data)
+    filename = utils.attachment_save(export_data)
+    return JSONResponse({'filename': filename})
+
+
+@csrf_exempt
+@permission_required('goods.add_goods')
+def goods_save(request):
+    id = request.GET.get('id')
+    data = json.loads(request.body)
+    try:
+        with transaction.atomic():
+            product_base_id = None
+            data['type'] = ProductBase.GOODS
+            serializer = GoodsSerializer.factory(request.user, data, id)
+            if serializer.instance:
+                product_base_id = serializer.instance.product_base_id
+            data['standard'] = data['base_standard']
+            pb = ProductBaseSerializer.factory(request.user,data,product_base_id)
+            pb = pb.validSave()
+            data['product_base'] = pb.id
+            serializer.validSave()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'保存失败!')
+    return JSONResponse()
+
+@permission_required('goods.delete_goods')
+def goods_delete(request):
+    id = int(request.GET.get('id'))
+    try:
+        with transaction.atomic():
+            goods = Goods.getById(id)
+            product_base_id = goods.product_base.id
+            BizLog.objects.addnew(request.user, BizLog.DELETE, u"删除成品[%s],id=%d" % (goods.product_base.name, goods.id))
+            goods.delete()
+            WarehouseStock.removeStockByProduct(goods.product_base)
+            ProductBase.objects.filter(id=product_base_id).delete()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except ProtectedError:
+        return JSONError(u'该成品已被使用,禁止删除!')
+    except IntegrityError:
+        return JSONError(u'该成品已被使用,禁止删除!')
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'删除失败!')
+    return JSONResponse({})
+
+@csrf_exempt
+@permission_required('goods.import_goods')
+def goods_import(request):
+    file = request.FILES.get('excel_file')
+
+    try:
+        line = 2
+        importer = GoodsImporter()
+        excel_rows = importer.getExcelData(file)
+        with transaction.atomic():
+            for excel_row in excel_rows:
+                try:
+                    row = importer.validRow(excel_row)
+                    option = Option.getByName(row[u'类别'], Option.GOODS_MODE)
+                    data = {
+                        'name': row[u'名称'],
+                        'model': row[u'代码'],
+                        'option_type': option.id,
+                        'standard': row[u'标准'],
+                        'goods_pack': row[u'包装'],
+                        'warehouse_place': row[u'库位'],
+                        'retail_price': row[u'销售价'],
+                        'notes': row[u'备注'],
+                        'enabled': 1,
+                    }
+                    data['type'] = ProductBase.GOODS
+                    pb = ProductBaseSerializer.factory(request.user,data)
+                    pb = pb.validSave()
+                    data['product_base'] = pb.id
+                    serializer = GoodsSerializer.factory(request.user,data)
+                    serializer.validSave()
+                except CustomError,e:
+                    raise CustomError(u'第%d行:%s' % (line,e.get_error_msg()))
+                except Exception,e:
+                    raise CustomError(u'第%d行:%s' %(line,unicode(e)))
+                line += 1
+            BizLog.objects.addnew(request.user, BizLog.IMPORT, u"导入成品数据[%s]条" % (line - 2))
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'导入失败!')
+    return JSONResponse()
+
+@csrf_exempt
+@token_required
+def goods_select(request):
+    param = request.GET.get('param')
+    warehouse_id = request.GET.get('warehouse')
+    rows = Goods.objects.filter(product_base__enabled=True)
+
+    if param:
+        rows = rows.filter(
+            Q(product_base__name__icontains=param) | Q(product_base__model__icontains=param)
+        )
+    rows, total = utils.get_page_data(request, rows)
+    data = []
+    for row in rows:
+        record_data = []
+        if warehouse_id:
+            record_data = GetWarehouseSrockRecord.getRecord(row.product_base.id, warehouse_id)
+        item = {
+            'id': row.id,
+            'product_id': row.product_base.id,
+            'name': row.product_base.name,
+            'model': row.product_base.model,
+            'unit': row.product_base.unit,
+            'base_standard': row.product_base.standard,
+            'type_text': row.product_base.get_type_display(),
+            'option_type': row.product_base.option_type.name,
+            'retail_price': Formater.formatPriceShow(row.retail_price),
+            'standard': row.standard,
+            'goods_pack': row.goods_pack,
+            # 'entry_price': entry_price,
+            # 'total_cost': total_cost,
+            'record_data': record_data
+        }
+        data.append(item)
+    return DataGridJSONResponse(data, total)
+
+
+@permission_required('goods.view_goods_godown_entry')
+def godownentry_list(request):
+    product_notes = request.GET.get('product_notes')
+    warehouses_ids = Warehouse.getManagerWarehouses(request.user)
+    department_ids = request.user.getSubDepartmentIds()
+    user_ids = request.user.getSubEmployeeIds()
+    rows = GoodsGodownEntry.objects.filter(warehouse_id__in=warehouses_ids)
+    rows = rows.filter(
+        Q(department_id__in=department_ids) | Q(create_user_id__in=user_ids) | Q(create_user=request.user))
+
+    if product_notes:
+        g_ids = rows.values_list('id')
+        d_ids = GoodsGodownEntryDetail.objects.filter(main_id__in=g_ids, notes__icontains=product_notes).values_list('main_id')
+        rows = rows.filter(id__in=d_ids)
+    f = GoodsGodownEntryFilter(request.GET, queryset=rows)
+    total_row = f.qs.aggregate(total_count=Sum('total_count'), total_amount=Sum('total_amount'))
+    more = {
+        'total_count': Formater.formatCountShow(total_row['total_count']),
+        'total_amount': Formater.formatAmountShow(total_row['total_amount'])
+    }
+    rows, total = utils.get_page_data(request, f.qs)
+    serializer = GoodsGodownEntrySerializer(rows, many=True)
+    return DataGridJSONResponse(serializer.data, total, more)
+
+
+
+@csrf_exempt
+@permission_required('goods.add_goods_godown_entry')
+def godownentry_save(request):
+    id = request.GET.get('id')
+    main_data = json.loads(request.POST.get('main'))
+    items_data = json.loads(request.POST.get('item'))
+    try:
+        with transaction.atomic():
+
+            serializer = GoodsGodownEntrySerializer.factory(request.user, main_data, id)
+            if serializer.instance and serializer.instance.status == settings.PASS:
+                raise CustomError(u'该成品入库单已审核,禁止修改!')
+            serializer = serializer.validSave()
+            GoodsGodownEntryDetail.objects.filter(main=serializer).delete()
+            for item in items_data:
+                item['main'] = serializer.id
+                detail_serializer = GoodsGodownEntryDetailSerializer.factory(request.user, data=item)
+                detail_serializer.validSave()
+            serializer.update_total()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'保存失败!')
+
+    return JSONResponse()
+
+@token_required
+def godownentry_detail(request):
+    id = request.GET.get('id')
+    instance = GoodsGodownEntry.getById(id)
+    company = instance.department.getCompany()
+    if instance.status == settings.PASS:
+        status_text = u'已审核'
+    else:
+        status_text = u'待审核'
+    main_data = {
+        'id': instance.id,
+        'warehouse_id': instance.warehouse_id,
+        'warehouse_name': instance.warehouse.name,
+        'create_user': instance.create_user.name,
+        'create_time': Formater.formatStrTime(instance.create_time),
+        'total_count': Formater.formatCountShow(instance.total_count),
+        'total_amount': Formater.formatAmountShow(instance.total_amount),
+        'total_invoice_amount': Formater.formatAmountShow(instance.total_invoice_amount),
+        'status_text': status_text,
+        'check_user_text': instance.check_user and instance.check_user.name or ' ',
+        'check_time': Formater.formatStrTime(instance.create_time),
+        'notes': instance.notes,
+        'company': company.name,
+        'no': instance.no
+    }
+
+    data = {
+        'main_data': main_data,
+        'items_data': []
+    }
+    detail_rows = GoodsGodownEntryDetail.objects.filter(main=instance)
+    for detail_row in detail_rows:
+        item_data = {
+            'id': detail_row.id,
+            'goods_id': detail_row.goods_id,
+            'goods_name': detail_row.goods.product_base.name,
+            'goods_model': detail_row.goods.product_base.model,
+            'price': Formater.formatPriceShow(detail_row.price),
+            'count': Formater.formatCountShow(detail_row.count),
+            'amount': Formater.formatAmountShow(detail_row.amount),
+            'invoice_amount': Formater.formatAmountShow(detail_row.invoice_amount),
+            'unit': detail_row.goods.product_base.unit or '',
+            'warehouse_place': detail_row.goods.product_base.warehouse_place or '',
+            'notes': detail_row.notes or ''
+        }
+        data['items_data'].append(item_data)
+
+    return JSONResponse(data)
+
+
+@permission_required('goods.delete_goods_godown_entry')
+def godownentry_delete(request):
+    id = request.GET.get('id')
+    try:
+        with transaction.atomic():
+            instance = GoodsGodownEntry.getById(id)
+            if instance.status == settings.PASS:
+                raise CustomError(u'该成品入库单已审核,禁止删除!')
+
+            BizLog.objects.addnew(request.user, BizLog.DELETE, u"删除成品入库单[%s],id=%d" % (instance.no, instance.id))
+            GoodsGodownEntryDetail.objects.filter(main=instance).delete()
+            instance.delete()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except ProtectedError:
+        return JSONError(u'该成品入库单已被引用,禁止删除!')
+    except IntegrityError:
+        return JSONError(u'该成品入库单已被引用,禁止删除!')
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'删除失败!')
+
+    return JSONResponse({})
+
+
+@permission_required('goods.export_goods_godown_entry')
+def godownentry_export(request):
+    try:
+        warehouses_ids = Warehouse.getManagerWarehouses(request.user)
+        department_ids = request.user.getSubDepartmentIds()
+        user_ids = request.user.getSubEmployeeIds()
+        rows = GoodsGodownEntry.objects.filter(warehouse_id__in=warehouses_ids)
+        rows = rows.filter(
+            Q(department_id__in=department_ids) | Q(create_user_id__in=user_ids) | Q(create_user=request.user))
+        f = GoodsGodownEntryFilter(request.GET, queryset=rows)
+        serializer = GoodsGodownEntrySerializer(f.qs, many=True)
+        export_data = ExportChange.dict_to_obj(serializer)
+
+        is_show_cost = isHasPermissions(request.user, 'goods.view_goods_cost')
+        export_data = GoodsGodownEntryResource(is_show_cost).export(export_data)
+        filename = utils.attachment_save(export_data)
+        BizLog.objects.addnew(request.user, BizLog.EXPORT, u"导出成品入库单" )
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'导出列表失败!')
+    return JSONResponse({'filename': filename})
+
+
+@permission_required('goods.export_goods_godown_entry')
+def godownentry_export_detail(request):
+    id = request.GET.get('id')
+    try:
+        instance = GoodsGodownEntry.getById(id)
+        godown_entry_detail = GoodsGodownEntryDetail.objects.filter(main=instance)
+        serializer = GoodsGodownEntryDetailSerializer(godown_entry_detail, many=True)
+        export_data = ExportChange.dict_to_obj(serializer)
+
+        is_show_cost = isHasPermissions(request.user, 'goods.view_goods_cost')
+
+        export_data = GoodsGodownEntryDetailResource(is_show_cost).export(export_data)
+        filename = utils.attachment_save(export_data)
+        BizLog.objects.addnew(request.user, BizLog.EXPORT, u"导出成品入库单[%s]明细,id=%d" % (instance.no, instance.id))
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'导出明细失败!')
+    return JSONResponse({'filename': filename})
+
+
+@csrf_exempt
+@permission_required('goods.import_goods_godown_entry')
+def godownentry_import(request):
+    file = request.FILES.get('excel_file')
+    main_data = json.loads(request.POST.get('main_data'))
+
+    try:
+        line = 2
+        importer = GoodsGodownEntryImporter()
+        excel_rows = importer.getExcelData(file)
+        with transaction.atomic():
+            serializer = GoodsGodownEntrySerializer.factory(request.user, main_data)
+            serializer = serializer.validSave()
+            for excel_row in excel_rows:
+                try:
+                    row = importer.validRow(excel_row)
+                    model = row[u'成品代码']
+                    goods = Goods.objects.filter(product_base__model=model, product_base__type=ProductBase.GOODS)
+                    if goods.count() == 0:
+                        raise CustomError(u'成品代码不存在')
+                    elif goods.count() > 1:
+                        raise CustomError(u'成品代码重复,前往基础数据设置修改')
+                    else:
+                        goods = goods.first()
+                    items_data = {}
+                    items_data['goods'] = goods.id
+                    items_data['main'] = serializer.id
+                    items_data['price'] = row[u'单价']
+                    items_data['count'] = row[u'数量']
+                    items_data['invoice_amount'] = row[u'发票金额']
+                    items_data['notes'] = row[u'备注']
+
+                    detail_serializer = GoodsGodownEntryDetailSerializer.factory(request.user, items_data)
+                    detail_serializer.validSave()
+                except CustomError, e:
+                    raise CustomError(u'第%d行:%s' % (line, e.get_error_msg()))
+                except Exception, e:
+                    raise CustomError(u'第%d行:%s' % (line, unicode(e)))
+                line += 1
+            serializer.update_total()
+            BizLog.objects.addnew(request.user, BizLog.IMPORT, u"导入成品入库单[%s],id=%d" % (serializer.no, serializer.id))
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'导入失败!')
+    return JSONResponse()
+
+
+@permission_required('goods.check_goods_godown_entry')
+def godownentry_check(request):
+    id = request.GET.get('id')
+    try:
+        with transaction.atomic():
+            instance = GoodsGodownEntry.getById(id)
+            if instance.status == settings.PASS:
+                raise CustomError(u'该成品入库单已审核')
+            godownentry_details = GoodsGodownEntryDetail.objects.filter(main=instance)
+            for godownentry_detail in godownentry_details:
+                stock_record = BizWarehouse.entry(type=WarehouseRecord.RK_ZJ,
+                                                  product=godownentry_detail.goods.product_base,
+                                                  warehouse=instance.warehouse,
+                                                  supplier=None,
+                                                  entry_count=godownentry_detail.count,
+                                                  entry_price=godownentry_detail.price,
+                                                  entry_price2=godownentry_detail.price)
+                godownentry_detail.stock_record = stock_record
+                godownentry_detail.save()
+
+            instance.status = settings.PASS
+            instance.check_user = request.user
+            instance.check_time = timezone.now()
+            BizLog.objects.addnew(
+                request.user,
+                BizLog.CHECK,
+                u"审核原料入库[%s],id=%d" % (instance.no, instance.id),
+            )
+            instance.save()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'审核失败')
+    return JSONResponse({})
+
+
+@csrf_exempt
+@permission_required('goods.edit_goods_godown_entry')
+def godownentry_senior_edit(request):
+    def updateStock(product_base, warehouse):
+        sum_row = WarehouseRecord.objects.filter(
+            warehouse=warehouse,
+            product=product_base
+        ).aggregate(
+            count=Sum('count'),
+            amount=Sum('amount'),
+            amount2=Sum('amount2')
+        )
+        warehouse_stock = WarehouseStock.objects.filter(product=product_base, warehouse=warehouse).first()
+        if warehouse_stock:
+            warehouse_stock.count = sum_row['count'] or 0
+            warehouse_stock.amount = sum_row['amount'] or 0
+            warehouse_stock.amount2 = sum_row['amount2'] or 0
+            if warehouse_stock.count != 0:
+                warehouse_stock.avg_cost_price = warehouse_stock.amount / warehouse_stock.count
+                warehouse_stock.avg_cost_price2 = warehouse_stock.amount2 / warehouse_stock.count
+            warehouse_stock.save()
+
+        sum_row = WarehouseStock.objects.filter(
+            product=product_base
+        ).aggregate(
+            count=Sum('count'),
+            amount=Sum('amount'),
+            amount2=Sum('amount2')
+        )
+
+        product_base.stock_count = sum_row['count'] or 0
+        product_base.stock_amount = sum_row['amount'] or 0
+        product_base.stock_amount2 = sum_row['amount2'] or 0
+        if product_base.stock_count > 0:
+            product_base.avg_cost_price = product_base.stock_amount / product_base.stock_count
+            product_base.avg_cost_price2 = product_base.stock_amount2 / product_base.stock_count
+        product_base.save()
+
+
+    def updateWarehouseRecord(warehouse_record):
+        sum_row = WarehouseRecordDetail.objects.filter(
+            warehouse_record=warehouse_record
+        ).aggregate(
+            sum_amount=Sum(F('count') * F('warehouse_stock_record__entry_price')),
+            sum_amount2=Sum(F('count') * F('warehouse_stock_record__entry_price2')),
+            sum_count=Sum('count')
+        )
+        warehouse_record.amount = sum_row['sum_amount'] or 0
+        warehouse_record.amount2 = sum_row['sum_amount2'] or 0
+        warehouse_record.count = sum_row['sum_count'] or 0
+        warehouse_record.save()
+
+    def changeEntryPrice(entry_detail,entry_price,entry_price2):
+        changeEntryPriceBase(entry_detail, entry_price, entry_price2)
+        rows = WarehouseRecordDetail.objects.filter(warehouse_stock_record=entry_detail.stock_record)
+
+        ck_type = (WarehouseRecord.CK_ZJ, WarehouseRecord.CK_XS,)
+        pk_type = (WarehouseRecord.CK_PK,)
+        tl_type = (WarehouseRecord.TL,)
+        for row in rows:
+            warehouse_record = row.warehouse_record
+            updateWarehouseRecord(warehouse_record)
+            if warehouse_record.type in ck_type:
+                deliver_detail = GoodsDeliverDetail.objects.filter(warehouse_record=warehouse_record).first()
+                if deliver_detail:
+                    #更新出库明细的合计成本
+                    deliver_detail.total_cost = warehouse_record.amount
+                    deliver_detail.save()
+
+                    # 更新出库单的合计成本
+                    order = deliver_detail.main
+                    sum_row = GoodsDeliverDetail.objects.filter(
+                        main=order
+                    ).aggregate(
+                        sum_cost=Sum('total_cost')
+                    )
+                    order.total_cost = sum_row['sum_cost'] or 0
+                    order.save()
+            elif warehouse_record.type in tl_type:
+                detail = GoodsDeliverReturnDetail.objects.filter(warehouse_record=warehouse_record).first()
+                if detail:
+                    #更新退料明细的合计成本
+                    old_amount = detail.return_cost
+                    detail.return_cost = warehouse_record.amount
+                    detail.save()
+
+                    # 更新出库明细的退料合计成本
+                    new_amount = detail.return_cost
+                    detail.deliver_detail.return_cost += new_amount - old_amount
+                    detail.deliver_detail.save()
+
+                    #更新出库单的退料合计成本
+                    detail.deliver_detail.main.return_cost += new_amount - old_amount
+                    detail.deliver_detail.main.save()
+
+                    #更新退料单的合计成本
+                    order = detail.main
+                    sum_row = GoodsDeliverReturnDetail.objects.filter(
+                        main=order
+                    ).aggregate(
+                        sum_cost=Sum('return_cost')
+                    )
+                    order.return_cost = sum_row['sum_cost'] or 0
+                    order.save()
+            elif warehouse_record.type in pk_type:
+                detail = InventoryDetail.objects.filter(warehouse_record=warehouse_record).first()
+                if detail:
+                    #更新盘存明细
+                    detail.amount = warehouse_record.amount
+                    detail.price = 0
+                    if detail.count:
+                        detail.price = detail.amount / detail.count
+                    detail.save()
+
+                    # 更新盘存单
+                    order = detail.main
+                    sum_row = InventoryDetail.objects.filter(
+                        main=order
+                    ).aggregate(
+                        sum_amount=Sum('amount')
+                    )
+                    order.total_amount = sum_row['sum_amount'] or 0
+                    order.save()
+
+
+    def changeEntryPriceBase(entry_detail,entry_price,entry_price2):
+        entry_detail.price = entry_price
+        entry_detail.amount = entry_detail.count * entry_price
+        entry_detail.save()
+
+        if entry_detail.stock_record:
+            entry_detail.stock_record.entry_price = entry_price
+            entry_detail.stock_record.entry_price2 = entry_price2
+            entry_detail.stock_record.save()
+
+    def changePrice(godownentry_detail,new_entry_price):
+        changeEntryPrice(godownentry_detail, new_entry_price, new_entry_price)
+        updateStock(godownentry_detail.goods.product_base, godownentry_detail.main.warehouse)
+
+    def addCount(godownentry_detail,add_count):
+        godownentry_detail.count += add_count
+        godownentry_detail.amount = godownentry_detail.count * godownentry_detail.price
+        godownentry_detail.save()
+        if godownentry_detail.stock_record:
+            godownentry_detail.stock_record.entry_count += add_count
+            godownentry_detail.stock_record.surplus_count += add_count
+            godownentry_detail.stock_record.save()
+
+            record_detail = WarehouseRecordDetail.objects.filter(
+                warehouse_stock_record=godownentry_detail.stock_record,
+                warehouse_record__type__in=(WarehouseRecord.RK_CG, WarehouseRecord.RK_ZJ)
+            ).first()
+            if record_detail:
+                record_detail.count += add_count
+                record_detail.save()
+                updateWarehouseRecord(record_detail.warehouse_record)
+        updateStock(godownentry_detail.goods.product_base, godownentry_detail.main.warehouse)
+
+    def decCount(godownentry_detail,red_count):
+        godownentry_detail.count -= red_count
+        godownentry_detail.amount = godownentry_detail.count * godownentry_detail.price
+        godownentry_detail.save()
+        if godownentry_detail.stock_record:
+            if red_count > godownentry_detail.stock_record.surplus_count:
+                raise CustomError(u'该入库单中的配件[%s],剩余%s,不能减少%s,请使用退货减少库存' % (
+                    godownentry_detail.goods.product_base.name,
+                    Formater.formatCountShow(godownentry_detail.stock_record.surplus_count),
+                    Formater.formatCountShow(red_count)
+                ))
+            godownentry_detail.stock_record.entry_count -= red_count
+            godownentry_detail.stock_record.surplus_count -= red_count
+            godownentry_detail.stock_record.save()
+
+            record_detail = WarehouseRecordDetail.objects.filter(
+                warehouse_stock_record=godownentry_detail.stock_record,
+                warehouse_record__type__in=(WarehouseRecord.RK_CG, WarehouseRecord.RK_ZJ)
+            ).first()
+            if record_detail:
+                record_detail.count -= red_count
+                record_detail.save()
+                updateWarehouseRecord(record_detail.warehouse_record)
+
+        updateStock(godownentry_detail.goods.product_base, godownentry_detail.main.warehouse)
+
+    new_rows = json.loads(request.POST.get('item'))
+    id = request.GET.get('id')
+
+    try:
+        with transaction.atomic():
+            godownentry = GoodsGodownEntry.objects.filter(pk=int(id)).first()
+            if not godownentry:
+                raise CustomError(u'未找到相应的入库单')
+            if godownentry.status != settings.PASS:
+                raise CustomError(u'未通过审核的入库单不允许高级修改')
+
+            for row in new_rows:
+                new_entry_count = Formater.formatCount(row['new_count'])
+                new_entry_price = Formater.formatPrice(row['new_price'])
+                if new_entry_count < 0:
+                    raise CustomError(u'入库数量不能小于0')
+                if new_entry_price < 0:
+                    raise CustomError(u'入库价不能小于0')
+
+                detail = GoodsGodownEntryDetail.objects.filter(id=int(row['new_detail_id'])).first()
+                if not detail:
+                    continue
+                if detail.price !=  new_entry_price:
+                    changePrice(detail,new_entry_price)
+                if detail.count < new_entry_count:
+                    addCount(detail,new_entry_count-detail.count)
+                if detail.count > new_entry_count:
+                    decCount(detail, detail.count-new_entry_count)
+
+                WarehouseRecord.updateCurStockByProduct(detail.main.warehouse_id, detail.goods.product_base_id)
+
+            count = 0
+            amount = 0
+            sum_row = GoodsGodownEntryDetail.objects.filter(main=godownentry).aggregate(
+                count_sum=Sum('count'),
+                amount_sum=Sum('amount')
+            )
+            if sum_row:
+                count = sum_row['count_sum'] or 0
+                amount = sum_row['amount_sum'] or 0
+            godownentry.total_count = count
+            godownentry.total_amount = amount
+            godownentry.save()
+
+            BizLog.objects.addnew(
+                request.user,
+                BizLog.UPDATE,
+                u"原料高级修改[单号=%s],id=%d" % (godownentry.no, godownentry.id)
+            )
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'修改失败')
+    return JSONResponse({})
+
+@csrf_exempt
+@permission_required('warehouse.view_goods_stock_log')
+def goods_stock_log(request):
+    keyword = request.GET.get('keyword')
+    action = request.GET.get('action')
+    happen_time = request.GET.get('happen_time')
+    warehouse_id = request.GET.get('warehouse_id')
+    product_id = request.GET.get('product_id')
+
+    page, page_size = utils.get_page_info(request)
+
+    product_id = int(product_id)
+    warehouse_id = int(warehouse_id)
+    where = ''
+
+    if happen_time:
+        happen_time = happen_time.split(' - ')
+        where += " AND m.happen_time >= '%s' " % happen_time[0]
+        where += " AND m.happen_time <= '%s' " % (happen_time[1] + ' 23:59:59')
+    if action:
+        where += ' AND m.type = %d' % int(action)
+
+    if keyword and len(keyword) > 0:
+        where += " AND (m.order_no like '%" + keyword + "%' " \
+                 " or m.warehouse_name like '%" + keyword + "%' )"
+
+    page_sql = ' LIMIT %d OFFSET %d ' % (page_size, page * page_size)
+    sql = """SELECT * FROM(
+    		  SELECT
+    			pwr.type,
+    			pwr.happen_time,
+    			(CASE WHEN pwr.type in (3, 4)  THEN gd.`no`
+    					WHEN pwr.type = 5 THEN iv.`no`
+    					WHEN pwr.type = 7 THEN gdr.`no`
+    					else '' END) AS order_no,
+    			(CASE WHEN pwr.type in (3, 4) THEN gdd.price
+    					WHEN pwr.type = 5 THEN ind.price
+    					WHEN pwr.type = 7 THEN rdgd.price
+    					else '' END) AS order_price,
+    			pwr.count,
+    			pwr.amount,
+    			pwr.amount/pwr.count as cost,
+    			pw.name as warehouse_name,
+    			pwr.warehouse_id AS warehouse_id,
+    			pwr.product_id AS product_id,
+    			pwr.id AS id,
+    			pwr.cur_count as cur_count,
+    			pwr.cur_amount as cur_amount
+    			FROM product_warehouse_record pwr LEFT JOIN
+    			product_warehouse pw ON pwr.warehouse_id = pw.id LEFT JOIN
+    			product_warehouse_stock pws ON pwr.product_id = pws.product_id AND pwr.warehouse_id=pws.warehouse_id LEFT JOIN
+    			goods_deliver_detail gdd ON pwr.id=gdd.warehouse_record_id LEFT JOIN
+    			goods_deliver gd ON gdd.main_id=gd.id LEFT JOIN
+    			goods_deliver_detail_return gdrd ON pwr.id = gdrd.warehouse_record_id LEFT JOIN
+    			goods_deliver_detail rdgd ON rdgd.id = gdrd.deliver_detail_id LEFT JOIN
+    			goods_deliver_return gdr ON gdrd.main_id = gdr.id LEFT JOIN
+    			inventory_detail ind on ind.warehouse_record_id=pwr.id left join 
+    			inventory iv ON iv.id=ind.main_id
+    			WHERE pwr.warehouse_id = %(warehose)d AND  pwr.product_id = %(product)d AND pwr.type >=3
+          UNION ALL
+    		SELECT
+    			pwr.type,
+    			pwr.happen_time,
+    			(CASE WHEN pwr.type in (0, 1) THEN gge.`no`
+    			            WHEN pwr.type = 2 THEN iv.`no`
+    						else '' END) AS order_no,
+    			(CASE WHEN pwr.type in (0, 1) THEN gded.price
+    			       WHEN pwr.type = 2 THEN ind.price
+    					else '' END) AS order_price,
+    			pwr.count,
+    			pwr.amount,
+    			pwr.amount/pwr.count as cost,
+    			pw.name as warehouse_name,
+    			pwr.warehouse_id AS warehouse_id,
+    			pwr.product_id AS product_id,
+    			pwr.id AS id,
+    			pwr.cur_count as cur_count,
+    			pwr.cur_amount as cur_amount
+    			FROM product_warehouse_record pwr LEFT JOIN
+    			product_warehouse pw ON pwr.warehouse_id = pw.id LEFT JOIN
+    			product_warehouse_stock pws ON pwr.product_id = pws.product_id AND pwr.warehouse_id=pws.warehouse_id LEFT JOIN
+    			product_warehouse_record_detail pwrd ON pwr.id=pwrd.warehouse_record_id LEFT JOIN
+    			goods_godown_entry_detail gded ON pwrd.warehouse_stock_record_id=gded.stock_record_id LEFT JOIN
+    			goods_godown_entry gge ON gded.main_id=gge.id LEFT JOIN
+    			inventory_detail ind on ind.warehouse_stock_record_id = pwrd.warehouse_stock_record_id left join 
+    			inventory iv ON iv.id = ind.main_id
+    			WHERE pwr.warehouse_id = %(warehose)d AND  pwr.product_id = %(product)d AND (pwr.type <= 2)
+    	        ) as m
+                WHERE 1=1 %(where)s
+                ORDER BY m.happen_time DESC
+                """ % {'warehose': warehouse_id, 'product': product_id, 'where': where}
+
+    sum_sql = """ SELECT
+                        COUNT(0)
+                        FROM
+                        (%s) AS t
+                        """ % sql
+    items = []
+    sql = '%s %s' % (sql, page_sql)
+    cursor = connection.cursor()
+    cursor.execute(sql)
+    row = cursor.fetchone()
+    total = 0
+    while row:
+        action_text = WarehouseRecord.TYPE_CHOICES[row[0]][1]
+        text = ''
+        if row[0] == WarehouseRecord.RK_ZJ:
+            text = u'入库单[' + row[2] + u']'
+        elif row[0] == WarehouseRecord.RK_CG:
+            text = u'采购转入库,入库单[' + row[2] + u']'
+        elif row[0] == WarehouseRecord.RK_PY:
+            text = u'盘盈单[' + row[2] + u']'
+        elif row[0] == WarehouseRecord.CK_ZJ:
+            text = u'出库单[' + row[2] + ']'
+        elif row[0] == WarehouseRecord.CK_XS:
+            text = u'订单转出库,出库单[' + row[2] + u']'
+        elif row[0] == WarehouseRecord.CK_PK:
+            text = u'盘亏单[' + row[2] + u']'
+        elif row[0] == WarehouseRecord.TH:
+            text = u'退货单[' + row[2] + u']'
+        elif row[0] == WarehouseRecord.TL:
+            text = u'退料单[' + row[2] + u']'
+
+        item = {
+            'action': row[0],
+            'action_text': action_text,
+            'create_time': strftime(row[1]),
+            'order_no': row[2],
+            'count': Formater.formatCountShow(row[4]),
+            'cost': Formater.formatPriceShow(row[6]),
+            'price': Formater.formatPriceShow(row[3]),
+            'warehouse_text': row[7],
+            'text': text,
+            'cur_count': Formater.formatCountShow(row[11]),
+            'cur_amount': Formater.formatAmountShow(row[12]),
+        }
+        items.append(item)
+        row = cursor.fetchone()
+
+    cursor.execute(sum_sql)
+    row = cursor.fetchone()
+    if row:
+        total = row[0]
+
+    return DataGridJSONResponse(items, total)
+
+@csrf_exempt
+@permission_required('product.view_goods_product')
+def goods_product_list(request):
+    empty = request.GET.get('empty')
+    rows = ProductBase.objects.filter(type=ProductBase.GOODS)
+    if empty == '0':
+        rows = rows.filter(stock_count=0)
+    elif empty == '1':
+        rows = rows.filter(stock_count__gt=0)
+
+    f = GoodsProductFilter(request.GET, queryset=rows)
+    total_row = f.qs.aggregate(sum_count=Sum('stock_count'))
+    more = {
+        'sum_count': Formater.formatCountShow(total_row['sum_count'])
+    }
+    rows, total = utils.get_page_data(request, f.qs)
+    serializer = GoodsProductSerializer(rows, many=True)
+    return DataGridJSONResponse(serializer.data, total, more)
+
+
+@csrf_exempt
+@permission_required('product.export_goods_product')
+def goods_product_export(request):
+    empty = request.GET.get('empty')
+    rows = ProductBase.objects.filter(type=ProductBase.GOODS)
+    if empty == '0':
+        rows = rows.filter(stock_count=0)
+    elif empty == '1':
+        rows = rows.filter(stock_count__gt=0)
+
+    f = GoodsProductFilter(request.GET, queryset=rows)
+    serializer = GoodsProductSerializer(f.qs, many=True)
+    export_data = ExportChange.dict_to_obj(serializer)
+    export_data = GoodsProductResource().export(export_data)
+    filename = utils.attachment_save(export_data)
+    return JSONResponse({'filename': filename})
+
+
+@token_required
+@permission_required('order.view_goods_godownentry_query')
+def godownentry_query_list(request):
+    rows = get_filter_data(request)
+    total_row = rows.aggregate(godownentry_total_count=Sum('goods_godown_entry_detail_ref_stock_record__count'),
+                               inventory_total_count=Sum('inventory_details_ref_warehouse_stock_record__count'),
+                               godownentry_total_amount=Sum('goods_godown_entry_detail_ref_stock_record__amount'),
+                               inventory_total_amount=Sum('inventory_details_ref_warehouse_stock_record__amount'),
+                               godownentry_total_deliver_count=Sum(
+                                   'goods_godown_entry_detail_ref_stock_record__deliver_count'),
+                               inventory_total_deliver_count=Sum(
+                                   'inventory_details_ref_warehouse_stock_record__deliver_count'),
+                               total_surplus_count=Sum('surplus_count')
+                               )
+    more = {
+        'total_count': Formater.formatCountShow(
+            (total_row['godownentry_total_count'] or 0) + (total_row['inventory_total_count'] or 0)),
+        'total_amount': Formater.formatAmountShow(
+            (total_row['godownentry_total_amount'] or 0) + (total_row['inventory_total_amount'] or 0)),
+        'total_deliver_count': Formater.formatCountShow(
+            (total_row['godownentry_total_deliver_count'] or 0) + (total_row['inventory_total_deliver_count'] or 0)),
+        'total_surplus_count': Formater.formatCountShow(total_row['total_surplus_count'] or 0)
+    }
+
+    rows, total = utils.get_page_data(request, rows)
+    data = get_godownentry_query_data(rows)
+    return DataGridJSONResponse(data, total, more)
+
+
+@token_required
+@permission_required('order.export_goods_godownentry_query')
+def godownentry_query_export(request):
+    try:
+        rows = get_filter_data(request)
+        data = get_godownentry_query_data(rows)
+        export_data = ExportChange.dict_to_obj2(data)
+
+        is_show_cost = isHasPermissions(request.user, 'goods.view_goods_cost')
+
+        export_data = GodownEntryQueryResource(is_show_cost).export(export_data)
+        filename = utils.attachment_save(export_data)
+        BizLog.objects.addnew(request.user, BizLog.EXPORT, u"导出成品入库查询")
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'导出成品入库查询失败!')
+    return JSONResponse({'filename': filename})
+
+
+@token_required
+def godownentry_query_detail(request):
+    rows = get_filter_data(request)
+    data = get_godownentry_query_data(rows)
+    return JSONResponse(data)
+
+
+def get_filter_data(request):
+    entry_time = request.GET.get('entry_time')
+    no = request.GET.get('no')
+    create_user = request.GET.get('create_user')
+    product_text = request.GET.get('product_text')
+    product_model = request.GET.get('product_model')
+    warehouse = request.GET.get('warehouse')
+    entry_type = request.GET.get('entry_type')
+    notes = request.GET.get('notes')
+    rows = WarehouseStockRecord.objects.filter(
+        product__type=ProductBase.GOODS,
+        warehouse_record_detail_ref_stock_record__warehouse_record__type__in=[0,1,2]).order_by(
+        '-id'
+    )
+    if entry_type:
+        rows = rows.filter(warehouse_record_detail_ref_stock_record__warehouse_record__type=int(entry_type))
+    if notes:
+        rows = rows.filter(Q(goods_godown_entry_detail_ref_stock_record__notes__icontains=notes) |
+                           Q(inventory_details_ref_warehouse_stock_record__notes__icontains=notes))
+    if product_text:
+        rows = rows.filter(product__name__icontains=product_text)
+    if product_model:
+        rows = rows.filter(product__model__icontains=product_model)
+    if warehouse:
+        rows = rows.filter(warehouse__name__icontains=warehouse)
+    if entry_time:
+        entry_time_begin = entry_time.split(' - ')[0]
+        entry_time_end = entry_time.split(' - ')[1] + ' 23:59:59'
+        rows = rows.filter(entry_time__gt=entry_time_begin, entry_time__lt=entry_time_end)
+
+    if no:
+        rows = rows.filter(Q(goods_godown_entry_detail_ref_stock_record__main__no__icontains=no) |
+                           Q(inventory_details_ref_warehouse_stock_record__main__no__icontains=no))
+    if create_user:
+        rows = rows.filter(Q(goods_godown_entry_detail_ref_stock_record__main__create_user__name__icontains=create_user) |
+                           Q(inventory_details_ref_warehouse_stock_record__main__create_user__name__icontains=create_user))
+    warehouses_ids = Warehouse.getManagerWarehouses(request.user)
+    department_ids = request.user.getSubDepartmentIds()
+    user_ids = request.user.getSubEmployeeIds()
+    rows = rows.filter(warehouse_id__in=warehouses_ids)
+    rows = rows.filter(Q(
+        Q(goods_godown_entry_detail_ref_stock_record__main__department_id__in=department_ids)
+        | Q(goods_godown_entry_detail_ref_stock_record__main__create_user_id__in=user_ids)
+        | Q(goods_godown_entry_detail_ref_stock_record__main__create_user=request.user)) |
+                       Q(
+        Q(inventory_details_ref_warehouse_stock_record__main__department_id__in=department_ids)
+        | Q(inventory_details_ref_warehouse_stock_record__main__create_user_id__in=user_ids)
+        | Q(inventory_details_ref_warehouse_stock_record__main__create_user=request.user)
+                       ))
+    return rows
+
+
+def get_godownentry_query_data(rows):
+
+    rows = rows.values(
+        'id',
+        'goods_godown_entry_detail_ref_stock_record__goods__product_base__name',
+        'goods_godown_entry_detail_ref_stock_record__goods__product_base__model',
+        'goods_godown_entry_detail_ref_stock_record__goods__product_base__unit',
+        'goods_godown_entry_detail_ref_stock_record__goods__product_base__type',
+        'goods_godown_entry_detail_ref_stock_record__goods__product_base__warehouse_place',
+
+        'inventory_details_ref_warehouse_stock_record__product__name',
+        'inventory_details_ref_warehouse_stock_record__product__model',
+        'inventory_details_ref_warehouse_stock_record__product__unit',
+        'inventory_details_ref_warehouse_stock_record__product__type',
+        'inventory_details_ref_warehouse_stock_record__product__warehouse_place',
+
+        'warehouse__name',
+        'warehouse_record_detail_ref_stock_record__warehouse_record__type',
+
+        'goods_godown_entry_detail_ref_stock_record__id',
+        'goods_godown_entry_detail_ref_stock_record__price',
+        'goods_godown_entry_detail_ref_stock_record__count',
+        'goods_godown_entry_detail_ref_stock_record__amount',
+        'goods_godown_entry_detail_ref_stock_record__deliver_count',
+        'goods_godown_entry_detail_ref_stock_record__notes',
+        'goods_godown_entry_detail_ref_stock_record__main__create_user__name',
+        'goods_godown_entry_detail_ref_stock_record__main__check_time',
+        'goods_godown_entry_detail_ref_stock_record__main__no',
+
+        'inventory_details_ref_warehouse_stock_record__price',
+        'inventory_details_ref_warehouse_stock_record__count',
+        'inventory_details_ref_warehouse_stock_record__amount',
+        'inventory_details_ref_warehouse_stock_record__notes',
+        'inventory_details_ref_warehouse_stock_record__deliver_count',
+        'inventory_details_ref_warehouse_stock_record__main__create_user__name',
+        'inventory_details_ref_warehouse_stock_record__main__check_time',
+        'inventory_details_ref_warehouse_stock_record__main__no',
+    )
+
+    data = []
+    for row in rows:
+        warehouse_record_type = WarehouseRecord.TYPE_CHOICES[row['warehouse_record_detail_ref_stock_record__warehouse_record__type']][1]
+        if row['warehouse_record_detail_ref_stock_record__warehouse_record__type'] == WarehouseRecord.RK_PY:
+            product_type_text = ProductBase.TYPE_CHOICES[row['inventory_details_ref_warehouse_stock_record__product__type']][1]
+            surplus_count = row['inventory_details_ref_warehouse_stock_record__count']  - row['inventory_details_ref_warehouse_stock_record__deliver_count']
+            item = {
+                'id': row['id'],
+                'type': warehouse_record_type,
+                'product_type': product_type_text,
+                'product_name': row['inventory_details_ref_warehouse_stock_record__product__name'],
+                'product_model': row['inventory_details_ref_warehouse_stock_record__product__model'],
+                'product_unit': row['inventory_details_ref_warehouse_stock_record__product__unit'],
+                'warehouse_place': row['inventory_details_ref_warehouse_stock_record__product__warehouse_place'],
+                'warehouse': row['warehouse__name'],
+                'price': Formater.formatPriceShow(row['inventory_details_ref_warehouse_stock_record__price']),
+                'count': Formater.formatCountShow(row['inventory_details_ref_warehouse_stock_record__count']),
+                'amount': Formater.formatAmountShow(row['inventory_details_ref_warehouse_stock_record__amount']),
+                'deliver_count': Formater.formatCountShow(row['inventory_details_ref_warehouse_stock_record__deliver_count']),
+                'surplus_count': Formater.formatCountShow(surplus_count),
+                'create_user': row['inventory_details_ref_warehouse_stock_record__main__create_user__name'],
+                'check_time': Formater.formatStrTime(row['inventory_details_ref_warehouse_stock_record__main__check_time']),
+                'notes': row['inventory_details_ref_warehouse_stock_record__notes'],
+                'no': row['inventory_details_ref_warehouse_stock_record__main__no'],
+            }
+        else:
+            product_type_text = ProductBase.TYPE_CHOICES[row['goods_godown_entry_detail_ref_stock_record__goods__product_base__type']][1]
+            surplus_count = row['goods_godown_entry_detail_ref_stock_record__count']  - row['goods_godown_entry_detail_ref_stock_record__deliver_count']
+            item = {
+                'id': row['id'],
+                'godownentry_detail_id': row['goods_godown_entry_detail_ref_stock_record__id'],
+                'type': warehouse_record_type,
+                'product_type': product_type_text,
+                'product_name': row['goods_godown_entry_detail_ref_stock_record__goods__product_base__name'],
+                'product_model': row['goods_godown_entry_detail_ref_stock_record__goods__product_base__model'],
+                'product_unit': row['goods_godown_entry_detail_ref_stock_record__goods__product_base__unit'],
+                'warehouse_place': row['goods_godown_entry_detail_ref_stock_record__goods__product_base__warehouse_place'],
+                'warehouse': row['warehouse__name'],
+                'price': Formater.formatPriceShow(row['goods_godown_entry_detail_ref_stock_record__price']),
+                'count': Formater.formatCountShow(row['goods_godown_entry_detail_ref_stock_record__count']),
+                'amount': Formater.formatAmountShow(row['goods_godown_entry_detail_ref_stock_record__amount']),
+                'deliver_count': Formater.formatCountShow(row['goods_godown_entry_detail_ref_stock_record__deliver_count']),
+                'surplus_count': Formater.formatCountShow(surplus_count),
+                'create_user': row['goods_godown_entry_detail_ref_stock_record__main__create_user__name'],
+                'check_time': Formater.formatStrTime(row['goods_godown_entry_detail_ref_stock_record__main__check_time']),
+                'notes': row['goods_godown_entry_detail_ref_stock_record__notes'],
+                'no': row['goods_godown_entry_detail_ref_stock_record__main__no'],
+            }
+        data.append(item)
+    return data

+ 0 - 0
apps/material/__init__.py


+ 75 - 0
apps/material/filters.py

@@ -0,0 +1,75 @@
+#coding=utf-8
+import django_filters
+from apps.base import clean_datetime_range
+from models import Material, Consumable, Deliver
+from apps.warehouse.models import Inventory
+
+
+class MaterialFilter(django_filters.FilterSet):
+    name = django_filters.CharFilter(name='product_base__name',lookup_expr='icontains')
+    model = django_filters.CharFilter(name='product_base__model', lookup_expr='icontains')
+    enabled = django_filters.CharFilter(name='product_base__enabled')
+
+    class Meta:
+        model = Material
+        fields = (
+            'name', 'model', 'enabled'
+        )
+
+    def __init__(self, data=None, *args, **kwargs):
+        data = clean_datetime_range(data, 'create_time')
+        super(MaterialFilter, self).__init__(data, *args, **kwargs)
+
+
+class ConsumableFilter(django_filters.FilterSet):
+    name = django_filters.CharFilter(name='product_base__name',lookup_expr='icontains')
+    model = django_filters.CharFilter(name='product_base__model', lookup_expr='icontains')
+    enabled = django_filters.CharFilter(name='product_base__enabled')
+
+    class Meta:
+        model = Consumable
+        fields = (
+            'name', 'model', 'enabled'
+        )
+
+    def __init__(self, data=None, *args, **kwargs):
+        data = clean_datetime_range(data, 'create_time')
+        super(ConsumableFilter, self).__init__(data, *args, **kwargs)
+
+
+class DeliverFilter(django_filters.FilterSet):
+    no = django_filters.CharFilter(name='no', lookup_expr='icontains')
+    create_user_text = django_filters.CharFilter(name='create_user__name', lookup_expr='icontains')
+    receiver_text = django_filters.CharFilter(name='receiver__name', lookup_expr='icontains')
+    create_time = django_filters.DateTimeFromToRangeFilter(field_name='create_time')
+
+    class Meta:
+        model = Deliver
+        fields = "__all__"
+
+    def __init__(self, data=None, *args, **kwargs):
+        data = clean_datetime_range(data, 'create_time', 'source')
+        super(DeliverFilter, self).__init__(data, *args, **kwargs)
+
+class InventoryFilter(django_filters.FilterSet):
+    create_time = django_filters.DateTimeFromToRangeFilter(field_name='create_time')
+    create_user_text = django_filters.CharFilter(name='create_user__name', lookup_expr='icontains')
+    no = django_filters.CharFilter(name='no', lookup_expr='icontains')
+
+    class Meta:
+        model = Inventory
+        fields = "__all__"
+
+    def __init__(self, data=None, *args, **kwargs):
+        data = clean_datetime_range(data, 'create_time', 'source')
+        super(InventoryFilter, self).__init__(data, *args, **kwargs)
+
+
+class DeliverReturnFilter(django_filters.FilterSet):
+    no = django_filters.CharFilter(name='no', lookup_expr='icontains')
+
+    class Meta:
+        model = Deliver
+        fields = (
+            'no',
+        )

+ 0 - 0
apps/material/migrations/__init__.py


+ 405 - 0
apps/material/models.py

@@ -0,0 +1,405 @@
+# coding=utf-8
+
+from django.db import models
+from django.db.models import Sum
+from django.utils import timezone
+from django.conf import settings
+from apps.account.models import User, Department
+from apps.exceptions import CustomError
+from apps.goods.models import Goods
+from apps.product.models import ProductBase
+from apps.warehouse.models import Warehouse, WarehouseStock, WarehouseRecord, WarehouseStockRecord
+
+
+class Material(models.Model):
+    NONE = 0
+    LOWER = 1
+    UPPER = 2
+    SUGGEST_CHOICES = (
+        (NONE, u'不开启'),
+        (LOWER, u'按库存下限预警'),
+        (UPPER, u'按库存上限预警'),
+    )
+    product_base = models.ForeignKey(ProductBase, verbose_name=u"产品", on_delete=models.PROTECT, blank=True)
+    htc_identification = models.CharField(max_length=100, verbose_name=u"产品标识", null=True, blank=True)
+    stock_upper_limit = models.BigIntegerField(verbose_name=u"库存上限", null=True, blank=True)
+    stock_lower_limit = models.BigIntegerField(verbose_name=u"库存下限", null=True, blank=True)
+    purchase_suggest = models.PositiveSmallIntegerField(choices=SUGGEST_CHOICES, verbose_name=u"库存预警", default=NONE)
+
+    class Meta:
+        db_table = "material"
+        verbose_name = u"原料产品管理"
+        ordering = ('-id',)
+        default_permissions = ()
+        permissions = (
+            ("view_material", u"浏览"),
+            ("add_material", u"添加"),
+            ("delete_material", u"删除"),
+            ("export_material", u"导出"),
+            ("import_material", u"导入"),
+            ("view_material_cost", u"查看成本"),
+        )
+
+    @staticmethod
+    def getById(id):
+        instance = Material.objects.filter(pk=id).first()
+        if not instance:
+            raise CustomError(u'未找到相应的原料')
+        return instance
+
+    @staticmethod
+    def getPurchaseSuggest():
+        purchasesuggest = {
+            u'不开启' : 0,
+            u'按库存下限预警': 1,
+            u'按库存上限预警': 2
+        }
+        return purchasesuggest
+
+    @staticmethod
+    def getPurchaseSuggestValue(value):
+        purchasesuggest = Material.getPurchaseSuggest()
+        try:
+            purchase_suggest = purchasesuggest[value]
+        except:
+            raise CustomError(u'库存预警填写不正确')
+        return purchase_suggest
+
+
+class Consumable(models.Model):
+    product_base = models.ForeignKey(ProductBase, verbose_name=u"产品", on_delete=models.PROTECT, blank=True)
+    stock_upper_limit = models.BigIntegerField(verbose_name=u"库存上限", null=True, blank=True)
+    stock_lower_limit = models.BigIntegerField(verbose_name=u"库存下限", null=True, blank=True)
+    purchase_suggest = models.PositiveSmallIntegerField(choices=Material.SUGGEST_CHOICES, verbose_name=u"库存预警", default=Material.NONE)
+
+    class Meta:
+        db_table = "consumable"
+        verbose_name = u"耗材产品管理"
+        ordering = ('-id',)
+        default_permissions = ()
+        permissions = (
+            ("view_consumable", u"浏览"),
+            ("add_consumable", u"添加"),
+            ("delete_consumable", u"删除"),
+            ("export_consumable", u"导出"),
+            ("import_consumable", u"导入"),
+            ("view_consumable_cost", u"查看成本"),
+        )
+
+    @staticmethod
+    def getById(id):
+        instance = Consumable.objects.filter(pk=id).first()
+        if not instance:
+            raise CustomError(u'未找到相应的耗材')
+        return instance
+
+
+class Deliver(models.Model):
+    MATERIAL = 0
+    CONSUMABLE = 1
+    PRODUCT_CHOICES = (
+        (MATERIAL, u'原料'),
+        (CONSUMABLE, u'耗材'),
+    )
+    PREFIX_CHOICES = (
+        (MATERIAL, 'CM'),
+        (CONSUMABLE, 'CC')
+    )
+    no = models.CharField(max_length=20, verbose_name=u"出库单号", editable=False)
+    product_type = models.PositiveSmallIntegerField(choices=PRODUCT_CHOICES, verbose_name=u"产品类型")
+
+    create_time = models.DateTimeField(verbose_name=u"创建时间", default=timezone.now)
+    create_user = models.ForeignKey(User, verbose_name=u"创建人", related_name='deliver_ref_create_user', on_delete=models.PROTECT, blank=True, editable=False)
+    department = models.ForeignKey(Department, verbose_name=u"创建部门", related_name='deliver_ref_department', on_delete=models.PROTECT, blank=True, editable=False)
+
+    receiver = models.ForeignKey(User, verbose_name=u"领用人", related_name='deliver_ref_receiver',on_delete=models.PROTECT)
+    receiver_department = models.ForeignKey(Department, verbose_name=u"领用车间", related_name='deliver_ref_receiver_department',
+                                            on_delete=models.PROTECT, blank=True, editable=False)
+
+    status = models.PositiveSmallIntegerField(choices=settings.CHECK_STATUS_CHOICES, verbose_name=u"审核状态", default=settings.DEFAULT)
+    check_user = models.ForeignKey(User, related_name='deliver_ref_check_user', verbose_name=u"审核人", on_delete=models.PROTECT, blank=True, null=True)
+    check_time = models.DateTimeField(verbose_name=u"审核时间", blank=True, null=True)
+
+    warehouse = models.ForeignKey(Warehouse, verbose_name=u'仓别', on_delete=models.PROTECT)
+    notes = models.CharField(max_length=200, verbose_name=u"备注", blank=True, null=True)
+    goods = models.ForeignKey(Goods, verbose_name=u'成品', on_delete=models.PROTECT, blank=True, null=True)
+
+    total_count = models.BigIntegerField(verbose_name=u'合计数量', default=0)
+    total_cost = models.BigIntegerField(verbose_name=u'合计成本', default=0)
+    return_count = models.BigIntegerField(verbose_name=u"合计退料数量", default=0)
+    return_cost = models.BigIntegerField(verbose_name=u"合计退料成本", default=0)
+
+
+    class Meta:
+        db_table = "deliver"
+        verbose_name = u"出库管理"
+        ordering = ('-id',)
+        index_together = (
+            'create_time', 'check_time', 'status'
+        )
+        unique_together = (
+            'no',
+        )
+        default_permissions = ()
+        permissions = (
+            ("view_material_deliver", u"浏览"),
+            ("add_material_deliver", u"添加"),
+            ("check_material_deliver", u"审核"),
+            ("delete_material_deliver", u"删除"),
+            ("export_material_deliver", u"导出"),
+            ("print_material_deliver", u"打印"),
+        )
+
+    def getPermission(self, action):
+            permissions = Deliver.getPermissionMap()
+            return permissions[self.product_type][action]
+
+    @staticmethod
+    def getById(id):
+        instances = Deliver.objects.filter(id=id).first()
+        if not instances:
+            raise CustomError(u'未找到相应的出库单')
+        return instances
+
+    @staticmethod
+    def getPermissionByType(type, action):
+        permissions = Deliver.getPermissionMap()
+        type = Deliver.getValidType(type)
+        return permissions[type][action]
+
+    @staticmethod
+    def getPermissionMap():
+        permissions = {
+            Deliver.MATERIAL: {'view': 'material.view_material_deliver',
+                                   'add': 'material.add_material_deliver',
+                                   'check': 'material.check_material_deliver',
+                                   'delete': 'material.delete_material_deliver',
+                                   'export': 'material.export_material_deliver',
+                                   'import': 'material.import_material_deliver',
+                                   'print': 'material.print_material_deliver'
+                                   },
+
+            Deliver.CONSUMABLE: {'view': 'material.view_consumable_deliver',
+                                     'add': 'material.add_consumable_deliver',
+                                     'check': 'material.check_consumable_deliver',
+                                     'delete': 'material.delete_consumable_deliver',
+                                     'export': 'material.export_consumable_deliver',
+                                     'import': 'material.import_consumable_deliver',
+                                     'print': 'material.print_consumable_deliver'
+                                     },
+        }
+        return permissions
+    
+    @staticmethod
+    def getValidType(type):
+        try:
+            type = int(type)
+        except:
+            raise CustomError(u'错误的出库类型')
+
+        types = (r[0] for r in Deliver.PRODUCT_CHOICES)
+        if type not in types:
+            raise CustomError(u'无效的出库类型')
+        return type
+
+    def save(self, *args, **kwargs):
+        if self.no == None or self.no == '':
+            now = timezone.now()
+            rows = Deliver.objects.filter(create_time__gte=now.strftime('%Y-%m-%d')).order_by('-no')
+            count = rows.count()
+            prefix = self.PREFIX_CHOICES[self.product_type][1]
+            if count == 0:
+                self.no = '%s%s%03d' % (prefix, now.strftime('%Y%m%d'), count + 1)
+            else:
+                self.no = rows[0].no[:2] + str(int(rows[0].no[2:]) + 1)
+        super(Deliver, self).save(*args, **kwargs)
+
+    def update_total(self):
+        total_count = 0
+        total_cost = 0
+        sum_row = DeliverDetail.objects.filter(main=self).aggregate(total_count=Sum('count'), total_cost=Sum('total_cost'))
+        if sum_row:
+            total_count = sum_row['total_count'] or 0
+            total_cost = sum_row['total_cost'] or 0
+        self.total_count = total_count
+        self.total_cost = total_cost
+        self.save()
+
+    def update_return(self):
+        return_count = 0
+        return_cost = 0
+        sum_row = DeliverDetail.objects.filter(main=self).aggregate(return_count=Sum('return_count'),
+                                                                    return_cost=Sum('return_cost'))
+        if sum_row:
+            return_count = sum_row['return_count'] or 0
+            return_cost = sum_row['return_cost'] or 0
+        self.return_count = return_count
+        self.return_cost = return_cost
+        self.save()
+        
+
+        
+class DeliverDetail(models.Model):
+    main = models.ForeignKey(Deliver, verbose_name=u'出库单', on_delete=models.PROTECT, related_name='deliver_details')
+    product_base = models.ForeignKey(ProductBase, verbose_name=u'产品', on_delete=models.PROTECT)
+    count = models.BigIntegerField(verbose_name=u'数量', default=0)
+    warehouse_stock = models.ForeignKey(WarehouseStock, verbose_name=u'仓别库存', on_delete=models.PROTECT, editable=False)
+    warehouse_record = models.ForeignKey(WarehouseRecord, verbose_name=u'库存记录', on_delete=models.PROTECT, null=True,blank=True, related_name='deliver_detail_ref_warehouse_record')
+    warehouse_stockrecord = models.ForeignKey(WarehouseStockRecord, verbose_name=u'入库库存记录', on_delete=models.PROTECT, null=True,blank=True, related_name='deliver_detail_ref_warehouse_stock_record')
+
+    total_cost = models.BigIntegerField(verbose_name=u'成本合计', default=0)
+    return_count = models.BigIntegerField(verbose_name=u'退料数量',default=0)
+    return_cost = models.BigIntegerField(verbose_name=u'退料成本合计', default=0)
+    notes = models.CharField(max_length=200, verbose_name=u"备注", blank=True, null=True)
+    
+    class Meta:
+        db_table = "deliver_detail"
+        verbose_name = u"出库管理"
+        ordering = ('-id',)
+        default_permissions = ()
+        permissions = (
+            ("view_consumable_deliver", u"浏览"),
+            ("add_consumable_deliver", u"添加"),
+            ("check_consumable_deliver", u"审核"),
+            ("delete_consumable_deliver", u"删除"),
+            ("export_consumable_deliver", u"导出"),
+            ("print_consumable_deliver", u"打印"),
+        )
+
+    def updateReturn(self):
+        return_count = 0
+        return_cost = 0
+        sum_row = DeliverReturnDetail.objects.filter(deliver_detail=self).aggregate(return_count=Sum('return_count'),
+                                                                                    return_cost=Sum('return_cost'))
+        if sum_row:
+            return_count = sum_row['return_count'] or 0
+            return_cost = sum_row['return_cost'] or 0
+        self.return_count = return_count
+        self.return_cost = return_cost
+        self.save()
+
+class DeliverReturn(models.Model):
+    PREFIX_CHOICES = (
+        (Deliver.MATERIAL, 'TM'),
+        (Deliver.CONSUMABLE, 'TC')
+    )
+    no = models.CharField(max_length=20, verbose_name=u"退料单号", editable=False)
+    product_type = models.PositiveSmallIntegerField(choices=Deliver.PRODUCT_CHOICES, verbose_name=u"产品类型")
+    reason = models.CharField(max_length=500, verbose_name=u"原因", editable=False)
+    create_time = models.DateTimeField(verbose_name=u"创建时间", default=timezone.now)
+    create_user = models.ForeignKey(User, verbose_name=u"创建人", on_delete=models.PROTECT, blank=True, editable=False)
+    department = models.ForeignKey(Department, verbose_name=u"创建部门", on_delete=models.PROTECT, blank=True, editable=False)
+    return_count = models.BigIntegerField(verbose_name=u'退料合计数量', default=0)
+    return_cost = models.BigIntegerField(verbose_name=u'退料合计成本', default=0)
+
+    class Meta:
+        db_table = "deliver_return"
+        verbose_name = u"退料管理"
+        ordering = ('-id',)
+        index_together = (
+            'create_time',
+        )
+        unique_together = (
+            'no',
+        )
+        default_permissions = ()
+        permissions = (
+            ("view_material_deliver_return", u"浏览"),
+            ("add_material_deliver_return", u"添加"),
+            ("print_material_deliver_return", u"打印"),
+        )
+
+    @staticmethod
+    def getByIds(ids):
+        return DeliverReturn.objects.filter(id__in=ids)
+
+    def getPermission(self, action):
+        permissions = DeliverReturn.getPermissionMap()
+        return permissions[self.product_type][action]
+
+    @staticmethod
+    def getById(id):
+        instances = DeliverReturn.objects.filter(id=id).first()
+        if not instances:
+            raise CustomError(u'未找到相应的退料单')
+        return instances
+
+    @staticmethod
+    def getPermissionByType(type, action):
+        permissions = DeliverReturn.getPermissionMap()
+        type = DeliverReturn.getValidType(type)
+        return permissions[type][action]
+
+    @staticmethod
+    def getPermissionMap():
+        permissions = {
+            Deliver.MATERIAL: {'view': 'material.view_material_deliver_return',
+                               'add': 'material.add_material_deliver_return',
+                               'print': 'material.print_material_deliver_return'
+                               },
+
+            Deliver.CONSUMABLE: {'view': 'material.view_consumable_deliver_return',
+                                 'add': 'material.add_consumable_deliver_return',
+                                 'print': 'material.print_consumable_deliver_return'
+                                 },
+        }
+        return permissions
+
+    @staticmethod
+    def getValidType(type):
+        try:
+            type = int(type)
+        except:
+            raise CustomError(u'错误的退料类型')
+
+        types = (r[0] for r in Deliver.PRODUCT_CHOICES)
+        if type not in types:
+            raise CustomError(u'无效的退料类型')
+        return type
+
+    def save(self, *args, **kwargs):
+        if self.no == None or self.no == '':
+            now = timezone.now()
+            rows = DeliverReturn.objects.filter(create_time__gte=now.strftime('%Y-%m-%d')).order_by('-no')
+            count = rows.count()
+            prefix = self.PREFIX_CHOICES[self.product_type][1]
+            if count == 0:
+                self.no = '%s%s%03d' % (prefix, now.strftime('%Y%m%d'), count + 1)
+            else:
+                self.no = rows[0].no[:2] + str(int(rows[0].no[2:]) + 1)
+        super(DeliverReturn, self).save(*args, **kwargs)
+
+    def update_total(self):
+        return_count = 0
+        return_cost = 0
+        sum_row = DeliverReturnDetail.objects.filter(main=self).aggregate(return_count=Sum('return_count'),
+                                                                          return_cost=Sum('return_cost'))
+        if sum_row:
+            return_count = sum_row['return_count'] or 0
+            return_cost = sum_row['return_cost'] or 0
+        self.return_count = return_count
+        self.return_cost = return_cost
+        self.save()
+
+
+class DeliverReturnDetail(models.Model):
+    main = models.ForeignKey(DeliverReturn, verbose_name=u'退料单', on_delete=models.PROTECT, related_name='deliver_return_details')
+    deliver_detail = models.ForeignKey(DeliverDetail, verbose_name=u'出库明细', on_delete=models.PROTECT)
+    product_base = models.ForeignKey(ProductBase, verbose_name=u'产品', on_delete=models.PROTECT)
+    return_count = models.BigIntegerField(verbose_name=u'退料数量', default=0)
+    return_cost = models.BigIntegerField(verbose_name=u'退料成本合计', default=0)
+    warehouse_stock = models.ForeignKey(WarehouseStock, verbose_name=u'仓别库存', on_delete=models.PROTECT, editable=False)
+    warehouse_record = models.ForeignKey(WarehouseRecord, verbose_name=u'库存记录', on_delete=models.PROTECT, null=True,
+                                         blank=True, related_name='deliver_return_detail_ref_warehouse_record')
+    notes = models.CharField(max_length=200, verbose_name=u"备注", blank=True, null=True)
+
+    class Meta:
+        db_table = "deliver_detail_return"
+        verbose_name = u"退料管理"
+        ordering = ('-id',)
+        default_permissions = ()
+        permissions = (# 退料管理明细
+            ("view_consumable_deliver_return", u"浏览"),
+            ("add_consumable_deliver_return", u"添加"),
+            ("print_consumable_deliver_return", u"打印"),
+        )

+ 281 - 0
apps/material/resources.py

@@ -0,0 +1,281 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+from import_export import resources
+from import_export.fields import Field
+
+from apps.base import ExcelImporter
+
+
+class MaterialResource(resources.Resource):
+
+    def __init__(self):
+        super(MaterialResource, self).__init__()
+        self.fields['name'] = Field(attribute='name')
+        self.fields['model'] = Field(attribute='model')
+        self.fields['option_type_text'] = Field(attribute='option_type_text')
+        self.fields['unit'] = Field(attribute='unit')
+        self.fields['standard'] = Field(attribute='standard')
+        self.fields['warehouse_place'] = Field(attribute='warehouse_place')
+        self.fields['purchase_suggest_text'] = Field(attribute='purchase_suggest_text')
+        self.fields['stock_upper_limit'] = Field(attribute='stock_upper_limit')
+        self.fields['stock_lower_limit'] = Field(attribute='stock_lower_limit')
+        self.fields['enabled_text'] = Field(attribute='enabled_text')
+        self.fields['notes'] = Field(attribute='notes')
+
+    def get_export_headers(self):
+        return [u'名称', u'代码', u'类别', u'单位', u'规格', u'库位', u'库存预警', u'库存上限',  u'库存下限', u'在用', u'备注',]
+
+    class Meta:
+        fields = ('name', 'model', 'option_type_text', 'unit', 'standard', 'warehouse_place', 'purchase_suggest_text',
+                  'stock_upper_limit', 'stock_lower_limit', 'enabled_text', 'notes',)
+        export_order = fields
+
+
+class MaterialImporter(ExcelImporter):
+    fields = {
+        u'类别': (True, ExcelImporter.formatUnicode),
+        u'名称': (True, ExcelImporter.formatUnicode),
+        u'代码': (True, ExcelImporter.formatUnicode),
+        u'单位': (False, ExcelImporter.formatUnicode),
+        u'产品标识': (False, ExcelImporter.formatUnicode),
+        u'规格': (False, ExcelImporter.formatUnicode),
+        u'库位': (False, ExcelImporter.formatUnicode),
+        u'库存预警': (True, ExcelImporter.formatUnicode),
+        u'库存上限': (True, ExcelImporter.formatFloatGeZ),
+        u'库存下限': (True, ExcelImporter.formatFloatGeZ),
+        u'备注': (False, ExcelImporter.formatUnicode),
+    }
+
+
+class ConsumableResource(resources.Resource):
+
+    def __init__(self):
+        super(ConsumableResource, self).__init__()
+        self.fields['name'] = Field(attribute='name')
+        self.fields['model'] = Field(attribute='model')
+        self.fields['option_type_text'] = Field(attribute='option_type_text')
+        self.fields['unit'] = Field(attribute='unit')
+        self.fields['standard'] = Field(attribute='standard')
+        self.fields['warehouse_place'] = Field(attribute='warehouse_place')
+        self.fields['purchase_suggest_text'] = Field(attribute='purchase_suggest_text')
+        self.fields['stock_upper_limit'] = Field(attribute='stock_upper_limit')
+        self.fields['stock_lower_limit'] = Field(attribute='stock_lower_limit')
+        self.fields['enabled_text'] = Field(attribute='enabled_text')
+        self.fields['notes'] = Field(attribute='notes')
+
+    def get_export_headers(self):
+        return [u'名称', u'代码', u'类别', u'单位', u'规格', u'库位', u'库存预警', u'库存上限',  u'库存下限', u'在用', u'备注',]
+
+    class Meta:
+        fields = ('name', 'model', 'option_type_text', 'unit', 'standard', 'warehouse_place', 'purchase_suggest_text',
+                   'stock_upper_limit', 'stock_lower_limit', 'enabled_text', 'notes',)
+        export_order = fields
+
+
+class ConsumableImporter(ExcelImporter):
+    fields = {
+        u'类别': (True, ExcelImporter.formatUnicode),
+        u'名称': (True, ExcelImporter.formatUnicode),
+        u'代码': (True, ExcelImporter.formatUnicode),
+        u'单位': (False, ExcelImporter.formatUnicode),
+        u'规格': (False, ExcelImporter.formatUnicode),
+        u'库位': (False, ExcelImporter.formatUnicode),
+        u'库存预警': (True, ExcelImporter.formatUnicode),
+        u'库存上限': (True, ExcelImporter.formatFloatGeZ),
+        u'库存下限': (True, ExcelImporter.formatFloatGeZ),
+        u'备注': (False, ExcelImporter.formatUnicode),
+    }
+
+
+class DeliverResource(resources.Resource):
+
+    def __init__(self, is_show_cost=True):
+        super(DeliverResource, self).__init__()
+        self.is_show_cost = is_show_cost
+        self.fields['no'] = Field(attribute='no')
+        self.fields['status_text'] = Field(attribute='status_text')
+        self.fields['create_user_text'] = Field(attribute='create_user_text')
+        self.fields['create_time'] = Field(attribute='create_time')
+        self.fields['check_user_text'] = Field(attribute='check_user_text')
+        self.fields['check_time'] = Field(attribute='check_time')
+        self.fields['receiver_text'] = Field(attribute='receiver_text')
+        self.fields['receiver_department_text'] = Field(attribute='receiver_department_text')
+        self.fields['goods_text'] = Field(attribute='goods_text')
+        self.fields['warehouse_text'] = Field(attribute='warehouse_text')
+        self.fields['total_count'] = Field(attribute='total_count')
+        self.fields['notes'] = Field(attribute='notes')
+        if is_show_cost:
+            self.fields['total_cost'] = Field(attribute='total_cost')
+            fields = ('no', 'goods_text', 'receiver_text', 'receiver_department_text','warehouse_text', 'total_count', 'total_cost', 'create_time',
+                      'create_user_text', 'status_text', 'check_time', 'check_user_text', 'notes',)
+        else:
+            fields = ('no', 'goods_text', 'receiver_text', 'receiver_department_text', 'warehouse_text', 'total_count', 'create_time',
+                      'create_user_text', 'status_text', 'check_time', 'check_user_text', 'notes',)
+        self._meta.export_order = fields
+
+
+    def get_export_headers(self):
+        if self.is_show_cost:
+            return [u'出库单号', u'成品药', u'领用人',u'领用部门', u'仓别', u'合计数量', u'合计成本', u'创建时间', u'创建人', u'审核状态', u'审核时间', u'审核人' , u'备注']
+        return [u'出库单号', u'成品药', u'领用人',u'领用部门', u'仓别', u'合计数量', u'创建时间', u'创建人', u'审核状态', u'审核时间', u'审核人' , u'备注']
+
+
+class DeliverDetailResource(resources.Resource):
+
+    def __init__(self, is_show_cost=True):
+        super(DeliverDetailResource, self).__init__()
+        self.is_show_cost = is_show_cost
+        self.fields['name'] = Field(attribute='name')
+        self.fields['model'] = Field(attribute='model')
+        self.fields['unit_text'] = Field(attribute='unit_text')
+        self.fields['count'] = Field(attribute='count')
+        self.fields['no'] = Field(attribute='no')
+        self.fields['warehouse_stock_count'] = Field(attribute='warehouse_stock_count')
+        self.fields['notes'] = Field(attribute='notes')
+        if is_show_cost:
+            self.fields['total_cost'] = Field(attribute='total_cost')
+            fields = ('name', 'model', 'unit_text', 'no', 'count', 'warehouse_stock_count', 'total_cost', 'notes')
+        else:
+            fields = ('name', 'model', 'unit_text', 'no', 'count', 'warehouse_stock_count', 'notes')
+        self._meta.export_order=fields
+
+    def get_export_headers(self):
+        if self.is_show_cost:
+            return [u'产品名称', u'产品代码', u'单位', u'入库单号', u'数量', u'仓别库存', u'成本合计', u'备注']
+        return [u'名称', u'代码', u'单位', u'入库单号', u'数量', u'仓别库存', u'备注']
+
+class InventoryResource(resources.Resource):
+
+    def __init__(self, is_show_cost):
+        super(InventoryResource, self).__init__()
+        self.is_show_cost = is_show_cost
+        self.fields['no'] = Field(attribute='no')
+        self.fields['type_text'] = Field(attribute='type_text')
+        self.fields['total_count'] = Field(attribute='total_count')
+        self.fields['warehouse_text'] = Field(attribute='warehouse_text')
+        self.fields['create_user_text'] = Field(attribute='create_user_text')
+        self.fields['create_time'] = Field(attribute='create_time')
+        self.fields['check_status_text'] = Field(attribute='check_status_text')
+        self.fields['check_user_text'] = Field(attribute='check_user_text')
+        self.fields['check_time'] = Field(attribute='check_time')
+        self.fields['notes'] = Field(attribute='notes')
+        if is_show_cost:
+            self.fields['total_amount'] = Field(attribute='total_amount')
+            fields = (
+            'no', 'type_text', 'total_count', 'total_amount','warehouse_text', 'create_time','create_user_text', 'check_status_text',
+            'check_time', 'check_user_text', 'notes',)
+        else:
+            fields = (
+            'no', 'type_text', 'total_count', 'warehouse_text', 'create_time', 'create_user_text',  'check_status_text',
+            'check_time', 'check_user_text', 'notes',)
+        self._meta.export_order = fields
+
+    def get_export_headers(self):
+        if self.is_show_cost:
+            return [u'盘存单号', u'类别', u'合计数量', u'合计金额', u'仓别', u'创建时间', u'创建人', u'审核状态', u'审核时间', u'审核人', u'备注']
+        return [u'盘存单号', u'类别', u'合计数量', u'仓别', u'创建时间', u'创建人', u'审核状态', u'审核时间', u'审核人', u'备注']
+
+
+
+class InventoryDetailResource(resources.Resource):
+
+    def __init__(self, is_show_cost=True):
+        super(InventoryDetailResource, self).__init__()
+        self.is_show_cost = is_show_cost
+        self.fields['name'] = Field(attribute='name')
+        self.fields['model'] = Field(attribute='model')
+        self.fields['warehouse_text'] = Field(attribute='warehouse_text')
+        self.fields['entry_no'] = Field(attribute='entry_no')
+        self.fields['count'] = Field(attribute='count')
+        self.fields['notes'] = Field(attribute='notes')
+        if is_show_cost:
+            self.fields['price'] = Field(attribute='price')
+            self.fields['amount'] = Field(attribute='amount')
+            fields = ('name', 'model', 'warehouse_text', 'entry_no', 'count', 'price', 'amount', 'notes')
+        else:
+            fields = ('name', 'model', 'warehouse_text', 'entry_no', 'count', 'notes')
+        self._meta.export_order = fields
+
+    def get_export_headers(self):
+        if self.is_show_cost:
+            return [u'产品名称', u'产品代码', u'仓别', u'入库单', u'数量', u'单价', u'金额', u'备注']
+        return [u'产品名称', u'产品代码', u'仓别', u'入库单', u'备注']
+
+
+
+class DeliverQueryResource(resources.Resource):
+    def __init__(self, is_show_cost=True):
+        super(DeliverQueryResource, self).__init__()
+        self.is_show_cost = is_show_cost
+        self.fields['no'] = Field(attribute='no')
+        self.fields['type'] = Field(attribute='type')
+        self.fields['name'] = Field(attribute='name')
+        self.fields['model'] = Field(attribute='model')
+        self.fields['unit'] = Field(attribute='unit')
+        self.fields['entry_no'] = Field(attribute='entry_no')
+        self.fields['product_type'] = Field(attribute='product_type')
+        self.fields['warehouse'] = Field(attribute='warehouse')
+        self.fields['count'] = Field(attribute='count')
+        self.fields['cur_count'] = Field(attribute='cur_count')
+        self.fields['return_count'] = Field(attribute='return_count')
+        self.fields['receiver'] = Field(attribute='receiver')
+        self.fields['check_user'] = Field(attribute='check_user')
+        self.fields['happen_time'] = Field(attribute='happen_time')
+        self.fields['warehouse_place'] = Field(attribute='warehouse_place')
+        self.fields['notes'] = Field(attribute='notes')
+        if is_show_cost:
+            self.fields['total_cost'] = Field(attribute='total_cost')
+            self.fields['return_cost'] = Field(attribute='return_cost')
+            fields = ('no', 'type', 'name', 'model', 'unit', 'entry_no', 'product_type', 'warehouse', 'count',
+                      'cur_count', 'total_cost', 'return_count', 'return_cost', 'receiver', 'happen_time', 'check_user'
+                      , 'warehouse_place', 'notes')
+        else:
+            fields = ('no', 'type', 'name', 'model', 'unit', 'entry_no', 'product_type', 'warehouse', 'count',
+                      'cur_count', 'return_count', 'receiver', 'happen_time', 'check_user',  'warehouse_place', 'notes')
+        self._meta.export_order = fields
+
+    def get_export_headers(self):
+        if self.is_show_cost:
+            return [u'出库单号', u'出库类别', u'产品', u'代码', u'单位', u'入库单号', u'产品类别', u'仓别', u'数量', u'剩余数量'
+                , u'合计成本', u'退料数量', u'退料成本合计', u'领用人', u'审核时间',u'审核人', u'存放库位', u'备注']
+        return [u'出库单号', u'出库类别', u'产品', u'代码', u'单位', u'入库单号', u'产品类别', u'仓别', u'数量', u'剩余数量'
+                , u'退料数量', u'领用人', u'审核时间',u'审核人', u'存放库位', u'备注']
+
+
+class DeliverReturnQueryResource(resources.Resource):
+    def __init__(self, is_show_cost=True):
+        super(DeliverReturnQueryResource, self).__init__()
+        self.is_show_cost = is_show_cost
+        self.fields['no'] = Field(attribute='no')
+        self.fields['return_no'] = Field(attribute='return_no')
+        self.fields['type'] = Field(attribute='type')
+        self.fields['name'] = Field(attribute='name')
+        self.fields['model'] = Field(attribute='model')
+        self.fields['product_type'] = Field(attribute='product_type')
+        self.fields['warehouse'] = Field(attribute='warehouse')
+        self.fields['cur_count'] = Field(attribute='cur_count')
+        self.fields['return_count'] = Field(attribute='return_count')
+        if is_show_cost:
+            self.fields['return_cost'] = Field(attribute='return_cost')
+        self.fields['create_user'] = Field(attribute='create_user')
+        self.fields['create_time'] = Field(attribute='create_time')
+        self.fields['warehouse_place'] = Field(attribute='warehouse_place')
+        self.fields['reason'] = Field(attribute='reason')
+        self.fields['notes'] = Field(attribute='notes')
+        if is_show_cost:
+            fields = ('no', 'return_no', 'type', 'name', 'model', 'product_type', 'warehouse',
+                      'cur_count', 'return_count', 'return_cost', 'create_user', 'create_time'
+                      , 'warehouse_place', 'reason', 'notes')
+        else:
+            fields = ('no', 'return_no', 'type', 'name', 'model', 'product_type', 'warehouse',
+                      'cur_count', 'return_count', 'create_user', 'create_time'
+                      , 'warehouse_place', 'reason', 'notes')
+        self._meta.fields = fields
+
+    def get_export_headers(self):
+        if self.is_show_cost:
+            return [u'出库单号', u'退料单号', u'出库类别', u'产品', u'代码', u'产品类别', u'仓别', u'剩余数量'
+                , u'退料数量', u'退料成本合计', u'创建人',u'创建时间', u'存放库位', u'退料原因', u'备注']
+        return [u'出库单号', u'退料单号', u'出库类别', u'产品', u'代码', u'产品类别', u'仓别', u'剩余数量'
+                , u'退料数量', u'创建人',u'创建时间', u'存放库位', u'退料原因', u'备注']
+

+ 465 - 0
apps/material/serializers.py

@@ -0,0 +1,465 @@
+#coding=utf-8
+
+from rest_framework import serializers
+
+from apps import base
+from apps.account.models import User
+from apps.base import Formater
+from apps.exceptions import CustomError
+from apps.foundation.models import BizLog
+from apps.purchase.models import GodownEntryDetail
+from apps.serializer_errors import dump_serializer_errors
+from apps.warehouse.biz import BizWarehouse
+from models import Material, Consumable, Deliver, DeliverDetail, DeliverReturn, DeliverReturnDetail
+from libs.booleancharfield import BooleanCharField, CountShowCharField, AmountShowCharField, PriceShowCharField
+from apps.warehouse.models import Inventory, InventoryDetail, WarehouseStock
+from apps import base
+from apps.goods.models import GoodsGodownEntryDetail
+from apps.purchase.models import GodownEntryDetail
+from apps.product.models import ProductBase
+
+
+class MaterialSerializer(serializers.ModelSerializer):
+    name = serializers.CharField(source='product_base.name', read_only=True)
+    model = serializers.CharField(source='product_base.model', read_only=True)
+    option_type = serializers.CharField(source='product_base.option_type.id', read_only=True)
+    option_type_text = serializers.CharField(source='product_base.option_type.name', read_only=True)
+    unit = serializers.CharField(source='product_base.unit', read_only=True)
+    standard = serializers.CharField(source='product_base.standard', read_only=True)
+    warehouse_place = serializers.CharField(source='product_base.warehouse_place', read_only=True)
+    purchase_suggest_text = serializers.CharField(source='get_purchase_suggest_display', read_only=True)
+    stock_upper_limit = CountShowCharField()
+    stock_lower_limit = CountShowCharField()
+    enabled_text = BooleanCharField(source='product_base.enabled', read_only=True)
+    enabled = serializers.BooleanField(source='product_base.enabled', read_only=True)
+    notes = serializers.CharField(source='product_base.notes', read_only=True)
+
+    class Meta:
+        model = Material
+        fields = '__all__'
+
+    @staticmethod
+    def factory(user, data, id=None):
+        if id:
+            instance = Material.getById(id)
+        else:
+            instance = None
+        serializer = MaterialSerializer(instance, data=data)
+        serializer.user = user
+        return serializer
+
+    def create(self, validated_data):
+        product = Material.objects.create(**validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加原料[%s],id=%d" % (product.product_base.name, product.id),
+            validated_data
+        )
+        return product
+
+    def update(self, instance, validated_data):
+        instance = super(MaterialSerializer, self).update(instance, validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.UPDATE,
+            u"修改原料[%s],id=%d" % (instance.product_base.name, instance.id),
+            validated_data
+        )
+        return instance
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+
+    def validate(self, data):
+        if 'stock_upper_limit' in data:
+            data['stock_upper_limit'] = Formater.formatCount(data['stock_upper_limit'])
+        if 'stock_lower_limit' in data:
+            data['stock_lower_limit'] = Formater.formatCount(data['stock_lower_limit'])
+        return data
+
+
+class ConsumableSerializer(serializers.ModelSerializer):
+    name = serializers.CharField(source='product_base.name', read_only=True)
+    model = serializers.CharField(source='product_base.model', read_only=True)
+    option_type = serializers.CharField(source='product_base.option_type.id', read_only=True)
+    option_type_text = serializers.CharField(source='product_base.option_type.name', read_only=True)
+    unit = serializers.CharField(source='product_base.unit', read_only=True)
+    standard = serializers.CharField(source='product_base.standard', read_only=True)
+    warehouse_place = serializers.CharField(source='product_base.warehouse_place', read_only=True)
+    purchase_suggest_text = serializers.CharField(source='get_purchase_suggest_display', read_only=True)
+    stock_upper_limit = CountShowCharField()
+    stock_lower_limit = CountShowCharField()
+    enabled_text = BooleanCharField(source='product_base.enabled', read_only=True)
+    enabled = serializers.BooleanField(source='product_base.enabled', read_only=True)
+    notes = serializers.CharField(source='product_base.notes', read_only=True)
+    class Meta:
+        model = Consumable
+        fields = '__all__'
+
+    @staticmethod
+    def factory(user, data, id=None):
+        if id:
+            instance = Consumable.getById(id)
+        else:
+            instance = None
+        serializer = ConsumableSerializer(instance, data=data)
+        serializer.user = user
+        return serializer
+
+    def create(self, validated_data):
+        product = Consumable.objects.create(**validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加耗材[%s],id=%d" % (product.product_base.name, product.id),
+            validated_data
+        )
+        return product
+
+    def update(self, instance, validated_data):
+        instance = super(ConsumableSerializer, self).update(instance, validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.UPDATE,
+            u"修改耗材[%s],id=%d" % (instance.product_base.name, instance.id),
+            validated_data
+        )
+        return instance
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+    def validate(self, data):
+        if 'stock_upper_limit' in data:
+            data['stock_upper_limit'] = Formater.formatCount(data['stock_upper_limit'])
+        if 'stock_lower_limit' in data:
+            data['stock_lower_limit'] = Formater.formatCount(data['stock_lower_limit'])
+        return data
+
+
+class DeliverSerializer(serializers.ModelSerializer):
+    status = serializers.CharField(read_only=True)
+    product_type_text = serializers.CharField(source='get_product_type_display', read_only=True)
+    status_text = serializers.CharField(source='get_status_display', read_only=True)
+    create_user_text = serializers.CharField(source='create_user.name', read_only=True)
+    create_time = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    check_user_text = serializers.CharField(source='check_user.name', read_only=True)
+    check_time = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    receiver_text = serializers.CharField(source='receiver.name', read_only=True)
+    receiver_department_text = serializers.CharField(source='receiver_department.name', read_only=True)
+    goods_text = serializers.CharField(source='goods.product_base.name', read_only=True)
+    warehouse_text = serializers.CharField(source='warehouse.name', read_only=True)
+    total_count = CountShowCharField(read_only=True)
+    total_cost = AmountShowCharField(read_only=True)
+
+    class Meta:
+        model = Deliver
+        fields = '__all__'
+
+    @staticmethod
+    def factory(user, data, id=None):
+        if id:
+            instance = Deliver.getById(id)
+        else:
+            instance = None
+        serializer =DeliverSerializer(instance, data=data)
+        serializer.user = user
+        return serializer
+
+    def create(self, validated_data):
+        validated_data['create_user'] = self.user
+        validated_data['department'] = self.user.department
+        deliver = Deliver.objects.create(**validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加原料/耗材出库单[%s],id=%d" % (deliver.no, deliver.id),
+            validated_data
+        )
+        return deliver
+
+    def update(self, instance, validated_data):
+        instance = super(DeliverSerializer, self).update(instance, validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.UPDATE,
+            u"修改原料/耗材出库单[%s],id=%d" % (instance.no, instance.id),
+            validated_data
+        )
+        return instance
+
+    def validate(self, data):
+        data['receiver_department'] = data['receiver'].department
+        return data
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+class DeliverDetailSerializer(serializers.ModelSerializer):
+    name = serializers.CharField(source='product_base.name', read_only=True)
+    model = serializers.CharField(source='product_base.model', read_only=True)
+    unit_text = serializers.CharField(source='product_base.unit', read_only=True)
+    no = serializers.SerializerMethodField()
+    count = CountShowCharField()
+    warehouse_stock_count = CountShowCharField(source='warehouse_stock.count', read_only=True)
+    total_cost = AmountShowCharField()
+
+    class Meta:
+        model = DeliverDetail
+        fields = '__all__'
+
+    @staticmethod
+    def factory(user, data):
+        serializer = DeliverDetailSerializer(None, data=data)
+        serializer.user = user
+        return serializer
+
+    def get_no(self, obj):
+        instance = GodownEntryDetail.objects.filter(stock_record=obj.warehouse_stockrecord).first()
+        if instance:
+            no = instance.main.no
+        else:
+            no = InventoryDetail.objects.filter(warehouse_stock_record=obj.warehouse_stockrecord).first().main.no
+        return no
+
+    def create(self, validated_data):
+        instance = DeliverDetail.objects.create(**validated_data)
+
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加原料/耗材出库明细[%s],id=%d" % (instance.product_base.name, instance.id),
+            validated_data
+        )
+        return instance
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+    def validate(self, data):
+        data['count'] = Formater.formatCount(data['count'])
+        data['total_cost'] = Formater.formatAmount(data['total_cost'])
+        warehouse_stock = WarehouseStock.getByWarehouseAndProduct(data['main'].warehouse, data['product_base'])
+        data['warehouse_stock'] = warehouse_stock
+        return data
+
+
+class InventorySerializer(serializers.ModelSerializer):
+    create_time = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    check_time = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    check_status_text = serializers.CharField(source='get_check_status_display', read_only=True)
+    check_user_text = serializers.CharField(source='check_user.name', read_only=True)
+    create_user_text = serializers.CharField(source='create_user.name', read_only=True)
+    type_text = serializers.CharField(source='get_type_display', read_only=True)
+    warehouse_text = serializers.CharField(source='warehouse.name', read_only=True)
+    total_count = CountShowCharField(read_only=True)
+    total_amount = AmountShowCharField(read_only=True)
+
+
+    class Meta:
+        model = Inventory
+        fields = '__all__'
+
+    @staticmethod
+    def factory(user, data, id=None):
+        if id:
+            instance = Inventory.getById(id)
+        else:
+            instance = None
+        serializer = InventorySerializer(instance, data=data)
+        serializer.user = user
+        return serializer
+
+    def create(self, validated_data):
+        validated_data['create_user'] = self.user
+        validated_data['department'] = self.user.department
+        order = Inventory.objects.create(**validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加盘存单[%s],id=%d" % (order.no, order.id),
+            validated_data
+        )
+        return order
+
+    def update(self, instance, validated_data):
+        instance = super(InventorySerializer, self).update(instance, validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.UPDATE,
+            u"修改盘存单[%s],id=%d" % (instance.no, instance.id),
+            validated_data
+        )
+        return instance
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+
+class InventoryDetailSerializer(serializers.ModelSerializer):
+    name = serializers.CharField(source='product.name', read_only=True)
+    model = serializers.CharField(source='product.model', read_only=True)
+    warehouse_text = serializers.CharField(source='main.warehouse.name', read_only=True)
+    entry_no = serializers.SerializerMethodField()
+    count = CountShowCharField()
+    price = PriceShowCharField()
+    amount = AmountShowCharField(read_only=True)
+
+    class Meta:
+        model = InventoryDetail
+        fields = '__all__'
+
+
+    def get_entry_no(self, obj):
+        if obj.product.type == ProductBase.GOODS:
+            g_row = GoodsGodownEntryDetail.objects.filter(stock_record_id=obj.loss_stock_record_id).first()
+        else:
+            g_row = GodownEntryDetail.objects.filter(stock_record_id=obj.loss_stock_record_id).first()
+
+        if not g_row:
+            g_row = InventoryDetail.objects.filter(warehouse_stock_record_id=obj.loss_stock_record_id).first()
+        return g_row and g_row.main.no or ''
+
+    @staticmethod
+    def factory(user, data):
+        instances = None
+        serializer = InventoryDetailSerializer(instances, data=data)
+        serializer.user = user
+        return serializer
+
+    def create(self, validated_data):
+        order = InventoryDetail.objects.create(**validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加盘存明细[%s],id=%d" % (order.main.no, order.id),
+            validated_data
+        )
+        return order
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+    def validate(self, data):
+        data['price'] = Formater.formatPrice(data['price'])
+        data['count'] = Formater.formatCount(data['count'])
+        data['amount'] = data['price'] * data['count']
+
+        warehouse_stock = WarehouseStock.getByWarehouseAndProduct(data['main'].warehouse, data['product'])
+        data['warehouse_stock'] = warehouse_stock
+        return data
+
+    def update(self, instance, validated_data):
+        instance = super(InventoryDetailSerializer, self).update(instance, validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"修改盘存明细[%s],id=%d" % (instance.main.no, instance.id),
+            validated_data
+        )
+        return instance
+
+
+class DeliverReturnViewSerializer(serializers.ModelSerializer):
+    create_user_text = serializers.CharField(source='create_user.name', read_only=True)
+    create_time = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    receiver_text = serializers.CharField(source='receiver.name', read_only=True)
+    warehouse_text = serializers.CharField(source='warehouse.name', read_only=True)
+    total_count = CountShowCharField(read_only=True)
+    total_cost = AmountShowCharField(read_only=True)
+    return_count = CountShowCharField(read_only=True)
+    return_cost = AmountShowCharField(read_only=True)
+
+    class Meta:
+        model = Deliver
+        fields = '__all__'
+
+
+class DeliverReturnSerializer(serializers.ModelSerializer):
+    create_time = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    class Meta:
+        model = DeliverReturn
+        fields = '__all__'
+
+    @staticmethod
+    def factory(user, data):
+        serializer = DeliverReturnSerializer(data=data)
+        serializer.user = user
+        return serializer
+
+    def create(self, validated_data):
+        validated_data['create_user'] = self.user
+        validated_data['department'] = self.user.department
+        deliver_return = DeliverReturn.objects.create(**validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加原料/耗材退料单[%s],id=%d" % (deliver_return.no, deliver_return.id),
+            validated_data
+        )
+        return deliver_return
+
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+
+class DeliverReturnDetailSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = DeliverReturnDetail
+        fields = '__all__'
+
+    @staticmethod
+    def factory(user, data):
+        serializer = DeliverReturnDetailSerializer(data=data)
+        serializer.user = user
+        return serializer
+
+    def create(self, validated_data):
+        deliver_return_detail = DeliverReturnDetail.objects.create(**validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加原料/耗材退料明细[%s],id=%d" % (deliver_return_detail.main.no, deliver_return_detail.id),
+            validated_data
+        )
+        return deliver_return_detail
+
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+    def validate(self, data):
+        data['return_count'] = Formater.formatCount(data['return_count'])
+        warehouse_stock = WarehouseStock.getByWarehouseAndProduct(data['deliver_detail'].main.warehouse, data['product_base'])
+        data['warehouse_stock'] = warehouse_stock
+        data['warehouse_record'] = BizWarehouse.deliveredBack(data['deliver_detail'].warehouse_record, data['return_count'])
+        data['return_cost'] = data['warehouse_record'].amount
+        return data

+ 58 - 0
apps/material/urls.py

@@ -0,0 +1,58 @@
+#coding=utf-8
+
+from django.conf.urls import url
+
+from views import *
+
+urlpatterns = (
+    url(r'^material/data/$', material_list),
+    url(r'^material/export/$', material_export),
+    url(r'^material/save/$', material_save),
+    url(r'^material/delete/$', material_delete),
+    url(r'^material/options/$', material_options),
+    url(r'^material/import/$', material_import),
+    url(r'^material/select/$', material_select),
+
+    url(r'^consumable/data/$', consumable_list),
+    url(r'^consumable/export/$', consumable_export),
+    url(r'^consumable/save/$', consumable_save),
+    url(r'^consumable/delete/$', consumable_delete),
+    url(r'^consumable/import/$',consumable_import),
+    url(r'^consumable/select/$',consumable_select),
+
+    url(r'^deliver/data/$', deliver_list),
+    url(r'^deliver/export/$', deliver_export),
+    url(r'^deliver/save/$', deliver_save),
+    url(r'^deliver/check/$', deliver_check),
+    url(r'^deliver/delete/$', deliver_delete),
+    url(r'^deliver/detail/$', deliver_detail),
+
+    url(r'^goods/select/$', goods_select),
+    url(r'^getamount/$', get_amount),
+    url(r'^editwarehouse/$', edit_warehouse),
+    url(r'^customer/select/$', get_customer),
+
+    url(r'^inventory/data/$', inventory_list),
+    url(r'^inventory/save/$', inventory_save),
+    url(r'^inventory/delete/$', inventory_delete),
+    url(r'^inventory/export/$', inventory_export),
+    url(r'^inventory/detail_export/$', inventory_detail_export),
+    url(r'^inventory/detail/$', inventory_detail),
+    url(r'^inventory/check/$', inventory_check),
+
+    url(r'^deliver_query/data/$', deliver_query_list),
+    url(r'^deliver_query/export/$', deliver_query_export),
+    url(r'^deliver_query/detail/$', deliver_query_detail),
+
+    url(r'^deliver_return/data/$', deliver_return_list),
+    url(r'^deliver_return/select_data/$', deliver_return_select_list),
+    url(r'^deliver_return/detail/$', deliver_return_detail),
+    url(r'^deliver_return/save/$', deliver_return_save),
+
+    url(r'^deliver_return_query/data/$', deliver_return_query_list),
+    url(r'^deliver_return_query/export/$', deliver_return_query_export),
+    url(r'^deliver_return_query/detail/$', deliver_return_query_detail),
+
+    url(r'^stock_log/$', stock_log),
+
+)

+ 1573 - 0
apps/material/views.py

@@ -0,0 +1,1573 @@
+# coding=utf-8
+
+import traceback
+import json
+
+from django.conf import settings
+from django.db.models import ProtectedError, Q, F, Sum
+from django.utils import timezone
+from django.views.decorators.csrf import csrf_exempt
+from django.db import transaction, IntegrityError, connection
+
+from apps.purchase.models import GodownEntryDetail
+from libs.utils import strftime, strfdate
+from apps.account.decorators import token_required, permission_required, valid_permission, isHasPermissions
+from apps.base import Formater
+from apps.customer.models import Customer
+from apps.foundation.models import BizLog, Option
+from apps.goods.models import Goods
+from apps.material.filters import MaterialFilter, ConsumableFilter, DeliverFilter, InventoryFilter, DeliverReturnFilter
+from apps.material.models import Material, Consumable, Deliver, DeliverDetail, DeliverReturn, DeliverReturnDetail
+from apps.material.resources import MaterialResource, ConsumableResource, ConsumableImporter, MaterialImporter, \
+    DeliverResource, DeliverDetailResource, InventoryResource, InventoryDetailResource, DeliverQueryResource, \
+    DeliverReturnQueryResource
+from apps.material.serializers import MaterialSerializer, ConsumableSerializer, DeliverSerializer, \
+    DeliverDetailSerializer, InventorySerializer, InventoryDetailSerializer, DeliverReturnSerializer, \
+    DeliverReturnViewSerializer, DeliverReturnDetailSerializer
+from apps.product.models import ProductBase
+from apps.product.serializers import ProductBaseSerializer
+from apps.warehouse.models import WarehouseStock, Inventory, InventoryDetail, Warehouse,WarehouseRecord
+from django.conf import settings
+from apps.warehouse.biz import BizWarehouse, GetWarehouseSrockRecord
+from apps.goods.models import GoodsGodownEntry,GoodsGodownEntryDetail
+from apps.purchase.models import GodownEntry,GodownEntryDetail
+
+from libs.http import JSONResponse, JSONError, DataGridJSONResponse
+from libs import utils
+from apps.exceptions import CustomError, ExportChange
+
+
+@permission_required('material.view_material')
+def material_list(request):
+    f = MaterialFilter(request.GET, queryset=Material.objects.filter())
+    rows, total = utils.get_page_data(request, f.qs)
+    serializer = MaterialSerializer(rows, many=True)
+    return DataGridJSONResponse(serializer.data, total)
+
+
+@permission_required('material.export_material')
+def material_export(request):
+    f = MaterialFilter(request.GET, queryset=Material.objects.filter())
+    serializer = MaterialSerializer(f.qs, many=True)
+    export_data = ExportChange.dict_to_obj(serializer)
+    export_data = MaterialResource().export(export_data)
+    filename = utils.attachment_save(export_data)
+    BizLog.objects.addnew(request.user, BizLog.EXPORT, u"导出原料单")
+    return JSONResponse({'filename': filename})
+
+@csrf_exempt
+@permission_required('material.add_material')
+def material_save(request):
+    id = request.GET.get('id')
+    data = json.loads(request.body)
+    try:
+        with transaction.atomic():
+            product_base_id = None
+            data['type'] = ProductBase.MATERIAL
+            serializer = MaterialSerializer.factory(request.user, data, id)
+            if serializer.instance:
+                product_base_id = serializer.instance.product_base_id
+
+            pb = ProductBaseSerializer.factory(request.user, data, product_base_id)
+            pb = pb.validSave()
+            data['product_base'] = pb.id
+            serializer.validSave()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'保存失败!')
+    return JSONResponse()
+
+@permission_required('material.delete_material')
+def material_delete(request):
+    id = int(request.GET.get('id'))
+
+    try:
+        with transaction.atomic():
+            material = Material.getById(id)
+            product_base_id = material.product_base.id
+            BizLog.objects.addnew(request.user, BizLog.DELETE, u"删除原料[%s],id=%d" % (material.product_base.name, material.id))
+            material.delete()
+            WarehouseStock.removeStockByProduct(material.product_base)
+            ProductBase.objects.filter(id=product_base_id).delete()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except ProtectedError:
+        return JSONError(u'该原料已被使用,禁止删除!')
+    except IntegrityError:
+        return JSONError(u'该原料已被使用,禁止删除!')
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'删除失败!')
+
+    return JSONResponse({})
+
+@csrf_exempt
+@token_required
+def material_options(request):
+    data = {}
+    data['suggest'] = Material.SUGGEST_CHOICES
+    return JSONResponse(data)
+
+
+@csrf_exempt
+@permission_required('material.import_material')
+def material_import(request):
+    file = request.FILES.get('excel_file')
+
+    try:
+        line = 2
+        importer = MaterialImporter()
+        excel_rows = importer.getExcelData(file)
+        with transaction.atomic():
+            for excel_row in excel_rows:
+                try:
+                    row = importer.validRow(excel_row)
+                    option = Option.getByName(row[u'类别'], Option.MATERIAL_MODE)
+                    data = {
+                        'name': row[u'名称'],
+                        'model': row[u'代码'],
+                        'option_type': option.id,
+                        'unit': row[u'单位'],
+                        'standard': row[u'规格'],
+                        'warehouse_place': row[u'库位'],
+                        'htc_identification': row[u'产品标识'],
+                        'purchase_suggest':  Material.getPurchaseSuggestValue(row[u'库存预警']),
+                        'stock_upper_limit': row[u'库存上限'],
+                        'stock_lower_limit': row[u'库存下限'],
+                        'notes': row[u'备注'],
+                        'enabled': 1,
+                    }
+                    data['type'] = ProductBase.MATERIAL
+                    pb = ProductBaseSerializer.factory(request.user, data)
+                    pb = pb.validSave()
+                    data['product_base'] = pb.id
+                    serializer = MaterialSerializer.factory(request.user, data)
+                    serializer.validSave()
+                except CustomError,e:
+                    raise CustomError(u'第%d行:%s' % (line,e.get_error_msg()))
+                except Exception,e:
+                    raise CustomError(u'第%d行:%s' %(line,unicode(e)))
+
+                line += 1
+            BizLog.objects.addnew(request.user, BizLog.IMPORT, u"导入原料数据[%s]条" % (line - 2))
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'导入失败!')
+    return JSONResponse()
+
+
+
+@token_required
+def material_select(request):
+    param = request.GET.get('param')
+    supplier = request.GET.get('supplier')
+    warehouse = request.GET.get('warehouse')
+    rows = Material.objects.filter(product_base__enabled=True)
+
+    if param:
+        rows = rows.filter(
+            Q(product_base__name__icontains=param) | Q(product_base__model__icontains=param)
+        )
+    rows, total = utils.get_page_data(request, rows)
+    data = []
+    for row in rows:
+        record_data = []
+        if supplier and warehouse:
+            record_data = GetWarehouseSrockRecord.getRecord(row.product_base.id, warehouse, supplier)
+
+        item = {
+            'id': row.id,
+            'product_id': row.product_base.id,
+            'name': row.product_base.name,
+            'model': row.product_base.model,
+            'option_type': row.product_base.option_type.name,
+            'unit': row.product_base.unit,
+            'warehouse_place': row.product_base.warehouse_place,
+            'record_data': record_data,
+            'standard': row.product_base.standard
+        }
+        data.append(item)
+    return DataGridJSONResponse(data, total)
+
+
+@permission_required('material.view_consumable')
+def consumable_list(request):
+    f = ConsumableFilter(request.GET, queryset=Consumable.objects.filter())
+    rows, total = utils.get_page_data(request, f.qs)
+    serializer = ConsumableSerializer(rows, many=True)
+    return DataGridJSONResponse(serializer.data, total)
+
+
+@permission_required('material.export_consumable')
+def consumable_export(request):
+    f = ConsumableFilter(request.GET, queryset=Consumable.objects.filter())
+    serializer = ConsumableSerializer(f.qs, many=True)
+    export_data = ExportChange.dict_to_obj(serializer)
+    export_data = ConsumableResource().export(export_data)
+    filename = utils.attachment_save(export_data)
+    BizLog.objects.addnew(request.user, BizLog.EXPORT, u"导出耗材单")
+    return JSONResponse({'filename': filename})
+
+@csrf_exempt
+@permission_required('material.add_consumable')
+def consumable_save(request):
+    id = request.GET.get('id')
+    data = json.loads(request.body)
+    try:
+        with transaction.atomic():
+            product_base_id = None
+            data['type'] = ProductBase.CONSUMABLE
+            serializer = ConsumableSerializer.factory(request.user, data, id)
+            if serializer.instance:
+                product_base_id = serializer.instance.product_base_id
+
+            pb = ProductBaseSerializer.factory(request.user, data, product_base_id)
+            pb = pb.validSave()
+            data['product_base'] = pb.id
+            serializer.validSave()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'保存失败!')
+    return JSONResponse()
+
+@permission_required('material.delete_consumable')
+def consumable_delete(request):
+    id = int(request.GET.get('id'))
+
+    try:
+        with transaction.atomic():
+            consumable = Consumable.getById(id)
+            product_base_id = consumable.product_base.id
+            BizLog.objects.addnew(request.user, BizLog.DELETE, u"删除耗材[%s],id=%d" % (consumable.product_base.name, consumable.id))
+            consumable.delete()
+            WarehouseStock.removeStockByProduct(consumable.product_base)
+            ProductBase.objects.filter(id=product_base_id).delete()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except ProtectedError:
+        return JSONError(u'该耗材已被使用,禁止删除!')
+    except IntegrityError:
+        return JSONError(u'该耗材已被使用,禁止删除!')
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'删除失败!')
+    return JSONResponse({})
+
+@csrf_exempt
+@permission_required('material.import_consumable')
+def consumable_import(request):
+    file = request.FILES.get('excel_file')
+
+    try:
+        line = 2
+        importer = ConsumableImporter()
+        excel_rows = importer.getExcelData(file)
+        with transaction.atomic():
+            for excel_row in excel_rows:
+                try:
+                    row = importer.validRow(excel_row)
+                    option = Option.getByName(row[u'类别'], Option.CONSUMABLE_MODE)
+                    data = {
+                        'name': row[u'名称'],
+                        'model': row[u'代码'],
+                        'option_type': option.id,
+                        'unit': row[u'单位'],
+                        'standard': row[u'规格'],
+                        'warehouse_place': row[u'库位'],
+                        'purchase_suggest': Material.getPurchaseSuggestValue(row[u'库存预警']),
+                        'stock_upper_limit': row[u'库存上限'],
+                        'stock_lower_limit': row[u'库存下限'],
+                        'notes': row[u'备注'],
+                        'enabled': 1,
+                    }
+                    data['type'] = ProductBase.CONSUMABLE
+                    pb = ProductBaseSerializer.factory(request.user,data)
+                    pb = pb.validSave()
+                    data['product_base'] = pb.id
+                    serializer = ConsumableSerializer.factory(request.user,data)
+                    serializer.validSave()
+                except CustomError,e:
+                    raise CustomError(u'第%d行:%s' % (line,e.get_error_msg()))
+                except Exception,e:
+                    raise CustomError(u'第%d行:%s' %(line,unicode(e)))
+                line += 1
+            BizLog.objects.addnew(request.user, BizLog.IMPORT, u"导入耗材数据[%s]条" % (line - 2))
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'导入失败!')
+    return JSONResponse()
+
+
+@token_required
+def consumable_select(request):
+    param = request.GET.get('param')
+    supplier = request.GET.get('supplier')
+    warehouse = request.GET.get('warehouse')
+    rows = Consumable.objects.filter(product_base__enabled=True)
+
+    if param:
+        rows = rows.filter(
+            Q(product_base__name__icontains=param) | Q(product_base__model__icontains=param)
+        )
+    rows, total = utils.get_page_data(request, rows)
+    data = []
+    for row in rows:
+        record_data = []
+        if supplier and warehouse:
+            record_data = GetWarehouseSrockRecord.getRecord(row.product_base.id, warehouse, supplier)
+        item = {
+            'id': row.id,
+            'product_id': row.product_base.id,
+            'name': row.product_base.name,
+            'model': row.product_base.model,
+            'option_type': row.product_base.option_type.name,
+            'unit': row.product_base.unit,
+            'warehouse_place': row.product_base.warehouse_place,
+            'record_data': record_data,
+            'standard': row.product_base.standard
+        }
+        data.append(item)
+    return DataGridJSONResponse(data, total)
+
+
+@token_required
+def deliver_list(request):
+    type = int(request.GET.get('type'))
+    product_notes = request.GET.get('product_notes')
+    try:
+        valid_permission(request.user, Deliver.getPermissionByType(type, 'view'))
+    except:
+        return DataGridJSONResponse([], 0)
+    warehouses_ids = Warehouse.getManagerWarehouses(request.user)
+    department_ids = request.user.getSubDepartmentIds()
+    user_ids = request.user.getSubEmployeeIds()
+    rows = Deliver.objects.filter(product_type=type, warehouse_id__in=warehouses_ids)
+    rows = rows.filter(
+        Q(department_id__in=department_ids) | Q(create_user_id__in=user_ids) | Q(create_user=request.user))
+    if product_notes:
+        g_ids = rows.values_list('id')
+        d_ids = DeliverDetail.objects.filter(main_id__in=g_ids, notes__icontains=product_notes).values_list('main_id')
+        rows = rows.filter(id__in=d_ids)
+    f = DeliverFilter(request.GET, queryset=rows)
+    total_row = f.qs.aggregate(total_count=Sum('total_count'), total_cost=Sum('total_cost'))
+    more = {
+        'total_count': Formater.formatCountShow(total_row['total_count']),
+        'total_cost': Formater.formatAmountShow(total_row['total_cost'])
+    }
+    rows, total = utils.get_page_data(request, f.qs)
+    serializer = DeliverSerializer(rows, many=True)
+    return DataGridJSONResponse(serializer.data, total, more)
+
+
+@csrf_exempt
+@token_required
+def deliver_export(request):
+    id = request.GET.get('id')
+
+    try:
+        type = Deliver.getValidType(request.GET.get('type'))
+        if type == Deliver.MATERIAL:
+            perm = 'material.view_material_cost'
+        elif type == Deliver.CONSUMABLE:
+            perm = 'material.view_consumable_cost'
+        is_show_cost = isHasPermissions(request.user, perm)
+        if id:
+            instance = Deliver.getById(id)
+            valid_permission(request.user, instance.getPermission('export'))
+            deliver_detail = DeliverDetail.objects.filter(main=instance)
+            serializer = DeliverDetailSerializer(deliver_detail, many=True)
+            export_data = ExportChange.dict_to_obj(serializer)
+            export_data = DeliverDetailResource(is_show_cost).export(export_data)
+            filename = utils.attachment_save(export_data)
+            BizLog.objects.addnew(request.user, BizLog.EXPORT, u"导出原料/耗材出库单[%s]明细,id=%d" % (instance.no, instance.id))
+        else:
+            warehouses_ids = Warehouse.getManagerWarehouses(request.user)
+            department_ids = request.user.getSubDepartmentIds()
+            user_ids = request.user.getSubEmployeeIds()
+            rows = Deliver.objects.filter(product_type=type, warehouse_id__in=warehouses_ids)
+            rows = rows.filter(
+                Q(department_id__in=department_ids) | Q(create_user_id__in=user_ids) | Q(create_user=request.user))
+            f = DeliverFilter(request.GET, queryset=rows)
+            serializer = DeliverSerializer(f.qs, many=True)
+            valid_permission(request.user, Deliver.getPermissionByType(type, 'export'))
+            export_data = ExportChange.dict_to_obj(serializer)
+            export_data = DeliverResource(is_show_cost).export(export_data)
+            filename = utils.attachment_save(export_data)
+            BizLog.objects.addnew(request.user, BizLog.EXPORT, u"导出原料/耗材出库单")
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'导出失败!')
+    return JSONResponse({'filename': filename})
+
+
+@csrf_exempt
+@token_required
+def deliver_save(request):
+    source = request.GET.get('source')
+    id = request.GET.get('id')
+    # detail_id:添加保存,出库直接保存,不需要添加明细detail_id=-1,添加明细保存默认detail_id=0,修改保存,detail_id=明细id
+    detail_id = -1
+    if source == 'touch':
+        main_data = json.loads(request.body)['main']
+        items_data = json.loads(request.body)['item']
+        detail_id = json.loads(request.body)['detail']
+    else:
+        main_data = json.loads(request.POST.get('main'))
+        items_data = json.loads(request.POST.get('item'))
+
+    try:
+        type = Deliver.getValidType(request.GET.get('type'))
+        main_data['product_type'] = type
+        with transaction.atomic():
+            serializer = DeliverSerializer.factory(request.user, main_data, id)
+            if serializer.instance and serializer.instance.status == settings.PASS:
+                raise CustomError(u'该出库单已审核,禁止修改!')
+            serializer = serializer.validSave()
+            valid_permission(request.user, serializer.getPermission('add'))
+            if source == 'touch' and detail_id >= 0:
+                # 手机端保存,如果是修改,先删除要修改的明细
+                DeliverDetail.objects.filter(id=int(detail_id)).delete()
+                for item in items_data:
+                    warehouse_stock = WarehouseStock.getByWarehouseAndProduct(serializer.warehouse,item['product_base'])
+                    instance = DeliverDetail.objects.create(
+                        main_id=id,
+                        product_base_id=item['product_base'],
+                        warehouse_stock=warehouse_stock,
+                        warehouse_stockrecord_id=item['warehouse_stockrecord'],
+                        total_cost=Formater.formatAmount(item['total_cost']),
+                        count=Formater.formatCount(item['count']),
+                        notes=item['notes'],
+                    )
+
+                    BizLog.objects.addnew(
+                        request.user,
+                        BizLog.INSERT,
+                        u"APP添加原料/耗材出库明细[%s],id=%d" % (instance.product_base.name, instance.id),
+                        item
+                    )
+            else:
+                DeliverDetail.objects.filter(main=serializer).delete()
+                for item in items_data:
+                    item['main'] = serializer.id
+                    detail_serializer = DeliverDetailSerializer.factory(request.user, data=item)
+                    detail_serializer.validSave()
+            serializer.update_total()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'保存失败!')
+    return JSONResponse(serializer.id)
+
+
+@token_required
+def deliver_delete(request):
+    id = request.GET.get('id')
+    try:
+        with transaction.atomic():
+            instance = Deliver.getById(id)
+            valid_permission(request.user, instance.getPermission('delete'))
+            if instance.status == settings.PASS:
+                raise CustomError(u'该出库单已审核,禁止删除!')
+
+            BizLog.objects.addnew(request.user, BizLog.DELETE, u"删除原料/耗材出库单[%s],id=%d" % (instance.no, instance.id))
+            DeliverDetail.objects.filter(main=instance).delete()
+            instance.delete()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except ProtectedError:
+        return JSONError(u'该出库单已被引用,禁止删除!')
+    except IntegrityError:
+        return JSONError(u'该出库单已被引用,禁止删除!')
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'删除失败!')
+
+    return JSONResponse({})
+
+
+@token_required
+def deliver_detail(request):
+    id = request.GET.get('id')
+    if not id:
+        return JSONResponse()
+    instance = Deliver.getById(id)
+    company = instance.department.getCompany()
+    if instance.status == settings.PASS:
+        status_text = u'已审核'
+    else:
+        status_text = u'待审核'
+    main_data = {
+        'id': instance.id,
+        'warehouse_id': instance.warehouse_id,
+        'warehouse_name': instance.warehouse.name,
+        'receiver_id': instance.receiver_id,
+        'receiver_name': instance.receiver and instance.receiver.name or '',
+        'receiver_department_name': instance.receiver_department and instance.receiver_department.name or '',
+        'goods_id': instance.goods_id,
+        'goods_name': instance.goods and instance.goods.product_base.name or '',
+        'create_user_name': instance.create_user.name,
+        'create_time': Formater.formatStrTime(instance.create_time),
+        'total_count': Formater.formatCountShow(instance.total_count),
+        'total_cost': Formater.formatAmountShow(instance.total_cost),
+        'status_text': status_text,
+        'status': instance.status,
+        'check_user_text': instance.check_user and instance.check_user.name or ' ',
+        'check_time': Formater.formatStrTime(instance.create_time),
+        'notes': instance.notes,
+        'no': instance.no,
+        'company': company.name
+    }
+
+    data = {
+        'main_data': main_data,
+        'items_data': []
+    }
+    detail_rows = DeliverDetail.objects.filter(main=instance)
+    for detail_row in detail_rows:
+        no = ''
+        if detail_row.warehouse_stockrecord_id:
+            godownentry = GodownEntryDetail.objects.filter(stock_record=detail_row.warehouse_stockrecord).first()
+            if godownentry:
+                no = godownentry.main.no
+            else:
+                no = InventoryDetail.objects.filter(
+                    warehouse_stock_record=detail_row.warehouse_stockrecord).first().main.no
+        record_data = GetWarehouseSrockRecord.getRecord(detail_row.product_base.id, instance.warehouse_id)
+        item_data = {
+            'detail_id': detail_row.id,
+            'id': detail_row.product_base_id,
+            'name': detail_row.product_base.name,
+            'model': detail_row.product_base.model,
+            'total_cost': Formater.formatAmountShow(detail_row.total_cost),
+            'count': Formater.formatCountShow(detail_row.count),
+            'entry_no': detail_row.warehouse_stockrecord_id,
+            'no': no,
+            'record_data': record_data,
+            'warehouse_stock_count': Formater.formatCountShow(detail_row.warehouse_stock.count),
+            'unit': detail_row.product_base.unit or '',
+            'notes': detail_row.notes or ''
+        }
+        data['items_data'].append(item_data)
+
+    return JSONResponse(data)
+
+@token_required
+def deliver_check(request):
+    id = request.GET.get('id')
+    try:
+        with transaction.atomic():
+            instance = Deliver.getById(id)
+            valid_permission(request.user, instance.getPermission('check'))
+            if instance.status == settings.PASS:
+                raise CustomError(u'该出库单已审核')
+            deliver_details = DeliverDetail.objects.filter(main=instance)
+            for deliver_detail in deliver_details:
+                warehouse_record = BizWarehouse.deliveredByStockRecord(WarehouseRecord.CK_ZJ,
+                                                  deliver_detail.warehouse_stockrecord,
+                                                  deliver_detail.count)
+                deliver_detail.warehouse_record = warehouse_record
+                deliver_detail.total_cost = -warehouse_record.amount
+                deliver_detail.save()
+            instance.update_total()
+            instance.status = settings.PASS
+            instance.check_user = request.user
+            instance.check_time = timezone.now()
+            BizLog.objects.addnew(
+                request.user,
+                BizLog.CHECK,
+                u"审核原料/耗材出库[%s],id=%d" % (instance.no, instance.id),
+            )
+
+            instance.save()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'审核失败')
+    return JSONResponse({})
+
+@token_required
+def goods_select(request):
+    goods = Goods.objects.filter(product_base__enabled=True)
+    data = []
+    for row in goods:
+        item = {
+            'id': row.id,
+            'value': row.product_base.name
+        }
+        data.append(item)
+    return JSONResponse(data)
+
+@token_required
+def get_amount(request):
+    product_id = request.GET.get('product_id')
+    warehouse = request.GET.get('warehouse')
+    count = request.GET.get('count')
+    try:
+        product = ProductBase.getById(product_id)
+        warehouse = Warehouse.getById(warehouse)
+        amount, amount2, max_entry_price, max_entry_price2 = BizWarehouse.tryDelivered(product, warehouse, Formater.formatCount(count))
+        data = {
+            'total_cost': Formater.formatAmountShow(amount),
+            'max_entry_price': Formater.formatPriceShow(max_entry_price)
+        }
+    except CustomError, e:
+        traceback.print_exc()
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'获取成本合计失败!')
+    return JSONResponse(data)
+
+@token_required
+def edit_warehouse(request):
+    rows = json.loads(request.POST.get('data'))
+    data = []
+    try:
+        for row in rows:
+            count = Formater.formatCount(row['count'])
+            product = ProductBase.getById(row['product_id'])
+            warehouse = Warehouse.getById(row['warehouse'])
+            amount, amount2, max_entry_price, max_entry_price2 = BizWarehouse.tryDelivered(product, warehouse, count)
+            item = {
+                'id': row['product_id'],
+                'num': row['num'],
+                'total_cost': Formater.formatAmountShow(amount),
+                'max_entry_price': Formater.formatPriceShow(max_entry_price)
+            }
+            data.append(item)
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'获取成本合计失败!')
+    return JSONResponse(data)
+
+@token_required
+def get_customer(request):
+    goods = Customer.objects.filter()
+    data = []
+    for row in goods:
+        item = {
+            'id': row.id,
+            'value': row.name
+        }
+        data.append(item)
+    return JSONResponse(data)
+
+
+@token_required
+def inventory_list(request):
+    product_type = int(request.GET.get('product_type'))
+    product_name = request.GET.get('product_name')
+    product_model = request.GET.get('product_model')
+    product_notes = request.GET.get('product_notes')
+    try:
+        valid_permission(request.user, Inventory.getPermissionByType(product_type, 'view'))
+    except:
+        return DataGridJSONResponse([], 0)
+    rows = Inventory.objects.filter(product_type=product_type)
+    if product_name:
+        ids = InventoryDetail.objects.filter(product__name__icontains=product_name).values_list('main_id')
+        rows = rows.filter(id__in=ids)
+    if product_model:
+        ids = InventoryDetail.objects.filter(product__model__icontains=product_model).values_list('main_id')
+        rows = rows.filter(id__in=ids)
+    if product_notes:
+        g_ids = rows.values_list('id')
+        d_ids = InventoryDetail.objects.filter(main_id__in=g_ids, notes__icontains=product_notes).values_list('main_id')
+        rows = rows.filter(id__in=d_ids)
+    warehouses_ids = Warehouse.getManagerWarehouses(request.user)
+    department_ids = request.user.getSubDepartmentIds()
+    user_ids = request.user.getSubEmployeeIds()
+    rows = rows.filter(warehouse_id__in=warehouses_ids)
+    rows = rows.filter(
+        Q(department_id__in=department_ids) | Q(create_user_id__in=user_ids) | Q(create_user=request.user))
+    f = InventoryFilter(request.GET, queryset=rows)
+    total_row = f.qs.aggregate(sum_count=Sum('total_count'), sum_amount=Sum('total_amount'))
+    more = {
+        'sum_count': Formater.formatCountShow(total_row['sum_count']),
+        'sum_amount': Formater.formatAmountShow(total_row['sum_amount'])
+    }
+
+    rows, total = utils.get_page_data(request, f.qs)
+    serializer = InventorySerializer(rows, many=True)
+    return DataGridJSONResponse(serializer.data, total, more)
+
+@csrf_exempt
+@token_required
+def inventory_save(request):
+    id = request.GET.get('id')
+    data = json.loads(request.body)
+
+    try:
+        with transaction.atomic():
+            pb = InventorySerializer.factory(request.user, data['order_data'], id)
+            if pb.instance and pb.instance.check_status == settings.PASS:
+                raise CustomError(u'该盘存单已审核')
+            pb = pb.validSave()
+            valid_permission(request.user, pb.getPermission('add'))
+            InventoryDetail.objects.filter(main_id=pb.id).delete()
+            for item in data['items']:
+                item['main'] = pb.id
+                pbd = InventoryDetailSerializer.factory(request.user, item)
+                pbd.validSave()
+
+            pb.updateAmount()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'保存失败')
+    return JSONResponse({})
+
+
+@csrf_exempt
+@token_required
+def inventory_delete(request):
+    id = int(request.GET.get('id'))
+    try:
+        with transaction.atomic():
+            order = Inventory.getById(id)
+            valid_permission(request.user, order.getPermission('delete'))
+            if order.check_status == settings.PASS:
+                raise CustomError(u'该盘存单已审核,禁止删除')
+            BizLog.objects.addnew(request.user, BizLog.DELETE, u"删除盘存单[%s],id=%d" % (order.no, order.id))
+            InventoryDetail.objects.filter(main__id=order.id).delete()
+            order.delete()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except ProtectedError:
+        return JSONError(u'该盘存单已被引用,禁止删除')
+    except IntegrityError:
+        return JSONError(u'该盘存单已被引用,禁止删除')
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'删除失败!')
+
+    return JSONResponse({})
+
+
+@csrf_exempt
+@token_required
+def inventory_detail(request):
+    id = int(request.GET.get('id'))
+    instance = Inventory.getById(id)
+    company = Inventory.getById(id).department.getCompany()
+    rows = InventoryDetail.objects.filter(main_id=id)
+    if instance.check_status == settings.PASS:
+        status_text = u'已审核'
+    else:
+        status_text = u'待审核'
+    if instance.type == instance.SURPLUS:
+        type_text = u'盘盈'
+    elif instance.type == instance.LOSS:
+        type_text = u'盘亏'
+    else:
+        type_text = ''
+    main_data = {
+        'id': instance.id,
+        'warehouse_id': instance.warehouse_id,
+        'warehouse_name': instance.warehouse.name,
+        'create_user_name': instance.create_user.name,
+        'create_time': Formater.formatStrTime(instance.create_time),
+        'status': instance.check_status,
+        'type_text': type_text,
+        'status_text': status_text,
+        'check_user_text': instance.check_user and instance.check_user.name or ' ',
+        'check_time': Formater.formatStrTime(instance.create_time),
+        'total_count': Formater.formatCountShow(instance.total_count),
+        'total_amount': Formater.formatAmountShow(instance.total_amount),
+        'notes': instance.notes or '',
+        'no': instance.no,
+        'company': company.name
+    }
+    data = {
+        'company': company.name,
+        'inventory_type':instance.type,
+        'main_data': main_data,
+        'items_data': [],
+    }
+    for row in rows:
+        record_data = GetWarehouseSrockRecord.getRecord(row.product_id, row.main.warehouse_id)
+        product_base = row.product
+
+        if product_base.type == ProductBase.GOODS:
+            g_row = GoodsGodownEntryDetail.objects.filter(stock_record_id=row.loss_stock_record_id).first()
+        else:
+            g_row = GodownEntryDetail.objects.filter(stock_record_id=row.loss_stock_record_id).first()
+
+        if not g_row:
+            g_row = InventoryDetail.objects.filter(warehouse_stock_record_id=row.loss_stock_record_id).first()
+
+        item = {
+            'id': row.id,
+            'product_id': row.product_id,
+            'loss_stock_record': row.loss_stock_record_id,
+            'product_name': row.product.name,
+            'unit': row.product.unit,
+            'product_model': row.product.model,
+            'warehouse_text': row.main.warehouse.name,
+            'surplus_count': row.loss_stock_record and Formater.formatCountShow(row.loss_stock_record.surplus_count) or '',
+            'count': Formater.formatCountShow(row.count),
+            'price': Formater.formatPriceShow(row.price),
+            'amount': Formater.formatAmountShow(row.amount),
+            'type_text': row.product.get_type_display(),
+            'option_type_text': row.product.option_type.name,
+            'notes': row.notes or '',
+            'entry_no': g_row and g_row.main.no or '',
+            'entry_notes': g_row and g_row.main.notes or '',
+            'record_data': record_data
+        }
+        data['items_data'].append(item)
+    return JSONResponse(data)
+
+
+@csrf_exempt
+@token_required
+def inventory_export(request):
+    product_type = int(request.GET.get('product_type'))
+    warehouses_ids = Warehouse.getManagerWarehouses(request.user)
+    department_ids = request.user.getSubDepartmentIds()
+    user_ids = request.user.getSubEmployeeIds()
+    rows = Inventory.objects.filter(product_type=product_type, warehouse_id__in=warehouses_ids)
+    rows = rows.filter(
+        Q(department_id__in=department_ids) | Q(create_user_id__in=user_ids) | Q(create_user=request.user))
+    f = InventoryFilter(request.GET, queryset=rows)
+    serializer = InventorySerializer(f.qs, many=True)
+    valid_permission(request.user, Inventory.getPermissionByType(product_type, 'export'))
+    export_data = ExportChange.dict_to_obj(serializer)
+    perm = ''
+    if product_type == Inventory.MATERIAL:
+        perm = 'material.view_material_cost'
+    elif product_type == Inventory.CONSUMABLE:
+        perm = 'material.view_consumable_cost'
+    is_show_cost = isHasPermissions(request.user, perm)
+    export_data = InventoryResource(is_show_cost).export(export_data)
+    filename = utils.attachment_save(export_data)
+    BizLog.objects.addnew(request.user, BizLog.EXPORT, u"导出盘存单")
+    return JSONResponse({'filename': filename})
+
+@csrf_exempt
+@token_required
+def inventory_detail_export(request):
+    id = request.GET.get('id')
+    order = Inventory.getById(id)
+    valid_permission(request.user, order.getPermission('export'))
+    serializer = InventoryDetailSerializer(InventoryDetail.objects.filter(main_id=id), many=True)
+    export_data = ExportChange.dict_to_obj(serializer)
+    perm = ''
+    if order.product_type == Inventory.MATERIAL:
+        perm = 'material.view_material_cost'
+    elif order.product_type == Inventory.CONSUMABLE:
+        perm = 'material.view_consumable_cost'
+    is_show_cost = isHasPermissions(request.user, perm)
+    export_data = InventoryDetailResource(is_show_cost).export(export_data)
+    filename = utils.attachment_save(export_data)
+    BizLog.objects.addnew(request.user, BizLog.EXPORT, u"导出盘存明细单")
+    return JSONResponse({'filename': filename})
+
+
+@csrf_exempt
+@token_required
+def inventory_check(request):
+    id = int(request.GET.get('id'))
+    try:
+        with transaction.atomic():
+            order = Inventory.getById(id)
+            valid_permission(request.user, order.getPermission('check'))
+            if order.check_status == settings.PASS:
+                raise CustomError(u'该盘存单已审核,不允许重复操作')
+
+            d_rows = InventoryDetail.objects.filter(main_id=id)
+            if order.type == Inventory.SURPLUS:
+                for d_row in d_rows:
+                    stock_record = BizWarehouse.entry(WarehouseRecord.RK_PY, d_row.product, d_row.main.warehouse, None, d_row.count, d_row.price, d_row.price)
+                    d_row.warehouse_stock_record = stock_record
+                    d_row.save()
+            else:
+                for d_row in d_rows:
+                    warehouse_record = BizWarehouse.deliveredByStockRecord(WarehouseRecord.CK_PK, d_row.loss_stock_record, d_row.count)
+
+                    if warehouse_record.count != 0:
+                        d_row.price = warehouse_record.amount / warehouse_record.count
+                    d_row.amount = -warehouse_record.amount
+                    d_row.warehouse_record = warehouse_record
+                    d_row.save()
+
+                order.updateAmount()
+
+            order.check_status = settings.PASS
+            order.check_user = request.user
+            order.check_time = timezone.now()
+            order.save()
+
+            BizLog.objects.addnew(
+                request.user,
+                BizLog.CHECK,
+                u"审核盘存单[%s],id=%d" % (order.no, order.id),
+            )
+
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'审核失败!')
+
+    return JSONResponse({})
+
+
+@token_required
+def deliver_query_list(request):# 原料耗材出库查询
+    try:
+        valid_permission(request.user, getPermissionByType(int(request.GET.get('type')), 'view'))
+    except:
+        return DataGridJSONResponse([], 0)
+    rows = get_filter_data(request)
+    total_row = rows.aggregate(total_count1=Sum('deliver_detail_ref_warehouse_record__count'),
+                               total_count2=Sum('inventory_details_ref_warehouse_record__count'),
+                               total_cost1=Sum('deliver_detail_ref_warehouse_record__total_cost'),
+                               total_cost2=Sum('inventory_details_ref_warehouse_record__amount'),
+                               return_count=Sum('deliver_detail_ref_warehouse_record__return_count'),
+                               return_cost=Sum('deliver_detail_ref_warehouse_record__return_cost'))
+    more = {
+        'total_count': Formater.formatCountShow((total_row['total_count1'] or 0) + (total_row['total_count2'] or 0)),
+        'total_cost': Formater.formatAmountShow((total_row['total_cost1'] or 0) + (total_row['total_cost2'] or 0)),
+        'return_count': Formater.formatCountShow(total_row['return_count']),
+        'return_cost': Formater.formatAmountShow(total_row['return_cost']),
+    }
+    rows, total = utils.get_page_data(request, rows)
+    data = get_deliver_query_data(rows)
+    return DataGridJSONResponse(data, total, more)
+
+@token_required
+def deliver_query_export(request):
+    type = int(request.GET.get('type'))
+    try:
+        valid_permission(request.user, getPermissionByType(type, 'export'))
+        rows = get_filter_data(request)
+        data = get_deliver_query_data(rows)
+        export_data = ExportChange.dict_to_obj2(data)
+        if type == ProductBase.MATERIAL:
+            perm = 'material.view_material_cost'
+        elif type == ProductBase.CONSUMABLE:
+            perm = 'material.view_consumable_cost'
+        is_show_cost = isHasPermissions(request.user, perm)
+        export_data = DeliverQueryResource(is_show_cost).export(export_data)
+        filename = utils.attachment_save(export_data)
+        BizLog.objects.addnew(request.user, BizLog.EXPORT, u"导出出库查询")
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'导出出库查询失败!')
+    return JSONResponse({'filename': filename})
+
+
+@token_required
+def deliver_query_detail(request):
+    rows = get_filter_data(request)
+    data = get_deliver_query_data(rows)
+    return JSONResponse(data)
+
+def get_filter_data(request):
+    product_type = int(request.GET.get('type'))
+    happen_time = request.GET.get('happen_time')
+    no = request.GET.get('no')
+    goods = request.GET.get('goods')
+    receiver = request.GET.get('receiver')
+    name = request.GET.get('name')
+    model = request.GET.get('model')
+    type = request.GET.get('deliver_type')
+    notes = request.GET.get('notes')
+    warehouse = request.GET.get('warehouse')
+    create_user = request.GET.get('create_user')
+    rows = WarehouseRecord.objects.filter(product__type=product_type, type__in=[3,4,5]).order_by('-id')
+    if happen_time:
+        happen_time_begin = happen_time.split(' - ')[0]
+        happen_time_end = happen_time.split(' - ')[1] + ' 23:59:59'
+        rows = rows.filter(happen_time__gt=happen_time_begin, happen_time__lt=happen_time_end)
+    if no:
+        rows = rows.filter(Q(deliver_detail_ref_warehouse_record__main__no__icontains=no) | Q(
+            inventory_details_ref_warehouse_record__main____no__icontains=no))
+    if notes:
+        rows = rows.filter(Q(deliver_detail_ref_warehouse_record__notes__icontains=notes) | Q(
+            inventory_details_ref_warehouse_record__notes__icontains=notes))
+    if receiver:
+        rows = rows.filter(deliver_detail_ref_warehouse_record__main__receiver__name__icontains=receiver)
+    if goods:
+        rows = rows.filter(deliver_detail_ref_warehouse_record__main__goods__product_base__name__icontains=goods)
+    if create_user:
+        rows = rows.filter(Q(deliver_detail_ref_warehouse_record__main__create_user__name__icontains=create_user) |
+                           Q(inventory_details_ref_warehouse_record__main__create_user__name__icontains=create_user))
+    if name:
+        rows = rows.filter(product__name__icontains=name)
+    if model:
+        rows = rows.filter(product__model__icontains=model)
+    if type:
+        rows = rows.filter(type=type)
+    if warehouse:
+        rows = rows.filter(warehouse__name__icontains=warehouse)
+    warehouses_ids = Warehouse.getManagerWarehouses(request.user)
+    department_ids = request.user.getSubDepartmentIds()
+    user_ids = request.user.getSubEmployeeIds()
+    rows = rows.filter(warehouse_id__in=warehouses_ids)
+    rows = rows.filter(Q(
+        Q(deliver_detail_ref_warehouse_record__main__department_id__in=department_ids)
+        | Q(deliver_detail_ref_warehouse_record__main__create_user_id__in=user_ids)
+        | Q(deliver_detail_ref_warehouse_record__main__create_user=request.user)) | Q(
+        Q(inventory_details_ref_warehouse_record__main__department_id__in=department_ids)
+        | Q(inventory_details_ref_warehouse_record__main__create_user_id__in=user_ids)
+        | Q(inventory_details_ref_warehouse_record__main__create_user=request.user)
+    ))
+    return rows
+
+def get_deliver_query_data(rows):
+    rows = rows.values(
+        'id',
+        'type',
+        'product__name',
+        'product__model',
+        'product__unit',
+        'product__type',
+        'product__warehouse_place',
+        'inventory_details_ref_warehouse_record__count',
+        'inventory_details_ref_warehouse_record__amount',
+        'warehouse__name',
+        'happen_time',
+        'cur_count',
+        'deliver_detail_ref_warehouse_record__count',
+        'deliver_detail_ref_warehouse_record__total_cost',
+        'deliver_detail_ref_warehouse_record__return_count',
+        'deliver_detail_ref_warehouse_record__return_cost',
+        'deliver_detail_ref_warehouse_record__notes',
+        'deliver_detail_ref_warehouse_record__main__receiver__name',
+        'deliver_detail_ref_warehouse_record__main__check_user__name',
+        'deliver_detail_ref_warehouse_record__main__create_user__name',
+        'deliver_detail_ref_warehouse_record__main__check_time',
+        'deliver_detail_ref_warehouse_record__main__goods__product_base__name',
+        'deliver_detail_ref_warehouse_record__main__no',
+        'deliver_detail_ref_warehouse_record__warehouse_stockrecord__godown_entry_detail_ref_stock_record__main__no',
+
+        'inventory_details_ref_warehouse_record__main__no',
+        'inventory_details_ref_warehouse_record__main__check_user__name',
+        'inventory_details_ref_warehouse_record__main__create_user__name',
+        'inventory_details_ref_warehouse_record__notes'
+        # 'inventory_details_ref_warehouse_record__loss_stock_record__godown_entry_detail_ref_stock_record__main__no',
+
+    )
+    data = []
+    for row in rows:
+        warehouse_record_type = WarehouseRecord.TYPE_CHOICES[row['type']][1]
+        product_type_text = ProductBase.TYPE_CHOICES[row['product__type']][1]
+        no = row['deliver_detail_ref_warehouse_record__main__no']
+        check_user = row['deliver_detail_ref_warehouse_record__main__check_user__name']
+        create_user = row['deliver_detail_ref_warehouse_record__main__create_user__name']
+        receiver = row['deliver_detail_ref_warehouse_record__main__receiver__name']
+        notes = row['deliver_detail_ref_warehouse_record__notes']
+        name = row['product__name']
+        model = row['product__model']
+        unit = row['product__unit']
+        entry_no = row['deliver_detail_ref_warehouse_record__warehouse_stockrecord__godown_entry_detail_ref_stock_record__main__no']
+        warehouse_place = row['product__warehouse_place']
+        count = Formater.formatCountShow(row['deliver_detail_ref_warehouse_record__count'])
+        total_cost = Formater.formatAmountShow(row['deliver_detail_ref_warehouse_record__total_cost'])
+        return_count = Formater.formatCountShow(row['deliver_detail_ref_warehouse_record__return_count'])
+        return_cost = Formater.formatAmountShow(row['deliver_detail_ref_warehouse_record__return_cost'])
+
+        if row['type'] == WarehouseRecord.CK_PK:
+            no = row['inventory_details_ref_warehouse_record__main__no']
+            # entry_no = row['inventory_details_ref_warehouse_record__loss_stock_record__godown_entry_detail_ref_stock_record__main__no']
+            check_user = row['inventory_details_ref_warehouse_record__main__check_user__name']
+            create_user = row['inventory_details_ref_warehouse_record__main__create_user__name']
+            receiver = ''
+            notes = row['inventory_details_ref_warehouse_record__notes']
+            count = Formater.formatCountShow(row['inventory_details_ref_warehouse_record__count'])
+            total_cost = Formater.formatAmountShow(row['inventory_details_ref_warehouse_record__amount'])
+            return_count = '0.00'
+            return_cost = '0.0000'
+        item = {
+            'id': row['id'],
+            'type': warehouse_record_type,
+            'product_type': product_type_text,
+
+            'name': name,
+            'model': model,
+            'unit': unit,
+            'warehouse_place': warehouse_place,
+
+            'warehouse': row['warehouse__name'],
+            'happen_time': Formater.formatStrTime(row['happen_time']),
+            'cur_count': Formater.formatCountShow(row['cur_count']),
+
+            'count': count,
+            'total_cost': total_cost,
+            'return_count': return_count,
+            'return_cost': return_cost,
+
+            'receiver': receiver,
+            'check_user': check_user,
+            'create_user': create_user,
+            'notes': notes,
+            'no': no,
+            'entry_no': entry_no
+        }
+        data.append(item)
+    return data
+
+def getPermissionByType(type, action):
+    permissions = {
+        ProductBase.MATERIAL: {'view': 'plan.view_material_deliver_query',
+                               'export': 'plan.export_material_deliver_query',
+                               },
+        ProductBase.CONSUMABLE: {'view': 'plan.view_consumable_deliver_query',
+                                 'export': 'plan.export_consumable_deliver_query',
+                               }
+    }
+    return permissions[type][action]
+
+@token_required
+def deliver_return_list(request):
+    type = int(request.GET.get('type'))
+    product_notes = request.GET.get('product_notes')
+    try:
+        valid_permission(request.user, DeliverReturn.getPermissionByType(type, 'view'))
+    except:
+        return DataGridJSONResponse([], 0)
+    warehouses_ids = Warehouse.getManagerWarehouses(request.user)
+    department_ids = request.user.getSubDepartmentIds()
+    user_ids = request.user.getSubEmployeeIds()
+    rows = Deliver.objects.filter(product_type=type, status=settings.PASS,
+                                                  warehouse_id__in=warehouses_ids)
+    rows = rows.filter(
+        Q(department_id__in=department_ids) | Q(create_user_id__in=user_ids) | Q(create_user=request.user))
+
+    if product_notes:
+        g_ids = rows.values_list('id')
+        d_ids = DeliverDetail.objects.filter(main_id__in=g_ids, notes__icontains=product_notes).values_list('main_id')
+        rows = rows.filter(id__in=d_ids)
+
+    f = DeliverFilter(request.GET, queryset=rows)
+    total_row = f.qs.aggregate(total_count=Sum('total_count'), total_cost=Sum('total_cost'),
+                               return_count=Sum('return_count'), return_cost=Sum('return_cost'))
+    more = {
+        'total_count': Formater.formatCountShow(total_row['total_count']),
+        'total_cost': Formater.formatAmountShow(total_row['total_cost']),
+        'return_count': Formater.formatCountShow(total_row['return_count']),
+        'return_cost': Formater.formatAmountShow(total_row['return_cost'])
+    }
+    rows, total = utils.get_page_data(request, f.qs)
+    serializer = DeliverReturnViewSerializer(rows, many=True)
+    return DataGridJSONResponse(serializer.data, total, more)
+
+@token_required
+def deliver_return_select_list(request):
+    id = int(request.GET.get('id'))
+
+    ids = DeliverReturnDetail.objects.filter(deliver_detail__main_id=id).values_list('main_id',flat=True)
+    rows = DeliverReturn.objects.filter(id__in=ids)
+    data = []
+    for row in rows:
+        data.append(row)
+    serializer = DeliverReturnSerializer(data, many=True)
+    return DataGridJSONResponse(serializer.data, rows.count())
+
+@token_required
+def deliver_return_detail(request):
+    id = request.GET.get('id')
+    ids = request.GET.get('ids')
+    data = {
+        'main_data': '',
+        'deliver_data': [],
+        'items_data': []
+    }
+    if ids:
+        id_list = ids.split(',')
+        deliver_returns = DeliverReturn.getByIds(id_list)
+        for deliver_return in deliver_returns:
+            deliver_data = {
+                'no': deliver_return.no,
+                'return_count': Formater.formatCountShow(deliver_return.return_count),
+                'return_cost': Formater.formatAmountShow(deliver_return.return_cost),
+                'reason': deliver_return.reason
+            }
+            data['deliver_data'].append(deliver_data)
+    instance = Deliver.getById(id)
+    company = instance.department.getCompany()
+    main_data = {
+        'warehouse_id': instance.warehouse_id,
+        'warehouse_name': instance.warehouse.name,
+        'receiver_id': instance.receiver_id,
+        'receiver_name': instance.receiver.name,
+        'goods_id': instance.goods_id,
+        'goods_name': instance.goods and instance.goods.product_base.name ,
+        'create_user_name': instance.create_user.name,
+        'create_time': Formater.formatStrTime(instance.create_time),
+        'total_count': Formater.formatCountShow(instance.total_count),
+        'total_cost': Formater.formatAmountShow(instance.total_cost),
+        'return_count': Formater.formatCountShow(instance.return_count),
+        'return_cost': Formater.formatAmountShow(instance.return_cost),
+        'notes': instance.notes,
+        'no': instance.no,
+        'company': company.name,
+    }
+    data['main_data'] = main_data
+    detail_rows = DeliverDetail.objects.filter(main=instance)
+    for detail_row in detail_rows:
+        item_data = {
+            'id': detail_row.id,
+            'product_id': detail_row.product_base_id,
+            'name': detail_row.product_base.name,
+            'model': detail_row.product_base.model,
+            'total_cost': Formater.formatAmountShow(detail_row.total_cost),
+            'count': Formater.formatCountShow(detail_row.count),
+            'cur_count': Formater.formatCountShow(detail_row.count - detail_row.return_count),
+            'warehouse_stock_count': Formater.formatCountShow(detail_row.warehouse_stock.count),
+            'unit':detail_row.product_base.unit or '',
+            'notes':detail_row.notes or ''
+        }
+        data['items_data'].append(item_data)
+
+    return JSONResponse(data)
+
+@token_required
+def deliver_return_save(request):
+    id = request.GET.get('id')
+    main_data = json.loads(request.POST.get('main'))
+    items_data = json.loads(request.POST.get('item'))
+    try:
+        type = DeliverReturn.getValidType(request.GET.get('type'))
+        main_data['product_type'] = type
+        with transaction.atomic():
+            serializer = DeliverReturnSerializer.factory(request.user, main_data)
+            serializer = serializer.validSave()
+            valid_permission(request.user, serializer.getPermission('add'))
+            for item in items_data:
+                item['main'] = serializer.id
+                detail_serializer = DeliverReturnDetailSerializer.factory(request.user, data=item)
+                detail_serializer = detail_serializer.validSave()
+                detail_serializer.deliver_detail.updateReturn()
+            serializer.update_total()
+            deliver = Deliver.getById(id)
+            deliver.update_return()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'保存失败!')
+    return JSONResponse()
+
+
+@token_required
+def deliver_return_query_list(request):# 原料耗材退料查询
+    try:
+        valid_permission(request.user, getPermissionByType(int(request.GET.get('type')), 'view'))
+    except:
+        return DataGridJSONResponse([], 0)
+    rows = get_return_filter_data(request)
+    total_row = rows.aggregate(return_count=Sum('deliver_return_detail_ref_warehouse_record__return_count'),
+                               return_cost=Sum('deliver_return_detail_ref_warehouse_record__return_cost'))
+    more = {
+        'return_count': Formater.formatCountShow(total_row['return_count']),
+        'return_cost': Formater.formatAmountShow(total_row['return_cost']),
+    }
+    rows, total = utils.get_page_data(request, rows)
+    data = get_deliver_return_query_data(rows)
+    return DataGridJSONResponse(data, total, more)
+
+@token_required
+def deliver_return_query_export(request):
+    type = int(request.GET.get('type'))
+    try:
+        valid_permission(request.user, getReturnPermissionByType(type, 'export'))
+        rows = get_return_filter_data(request)
+        data = get_deliver_return_query_data(rows)
+        export_data = ExportChange.dict_to_obj2(data)
+        if type == ProductBase.MATERIAL:
+            perm = 'material.view_material_cost'
+        elif type == ProductBase.CONSUMABLE:
+            perm = 'material.view_consumable_cost'
+        is_show_cost = isHasPermissions(request.user, perm)
+        export_data = DeliverReturnQueryResource(is_show_cost).export(export_data)
+        filename = utils.attachment_save(export_data)
+        BizLog.objects.addnew(request.user, BizLog.EXPORT, u"导出原料/耗材退料查询")
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'导出原料/耗材退料查询失败!')
+    return JSONResponse({'filename': filename})
+
+
+@token_required
+def deliver_return_query_detail(request):
+    rows = get_return_filter_data(request)
+    data = get_deliver_return_query_data(rows)
+    return JSONResponse(data)
+
+def get_return_filter_data(request):
+    product_type = int(request.GET.get('type'))
+    create_time = request.GET.get('create_time')
+    warehouse = request.GET.get('warehouse')
+    return_no = request.GET.get('return_no')
+    no = request.GET.get('no')
+    name = request.GET.get('name')
+    model = request.GET.get('model')
+    notes = request.GET.get('notes')
+    reason = request.GET.get('reason')
+    create_user = request.GET.get('create_user')
+    rows = WarehouseRecord.objects.filter(product__type=product_type, type=7).order_by('-id')
+    if create_time:
+        create_time_begin = create_time.split(' - ')[0]
+        create_time_end = create_time.split(' - ')[1] + ' 23:59:59'
+        rows = rows.filter(deliver_return_detail_ref_warehouse_record__main__create_time__gt=create_time_begin,
+                           deliver_return_detail_ref_warehouse_record__main__create_time__lt=create_time_end)
+    if no:
+        rows = rows.filter(deliver_return_detail_ref_warehouse_record__deliver_detail__main__no__icontains=no)
+    if notes:
+        rows = rows.filter(deliver_return_detail_ref_warehouse_record__notes__icontains=notes)
+    if create_user:
+        rows = rows.filter(deliver_return_detail_ref_warehouse_record__main__create_user__name__icontains=create_user)
+    if warehouse:
+        rows = rows.filter(deliver_return_detail_ref_warehouse_record__deliver_detail__main__warehouse__name__icontains=warehouse)
+    if return_no:
+        rows = rows.filter(
+            deliver_return_detail_ref_warehouse_record__main__no__icontains=return_no)
+    if reason:
+        rows = rows.filter(deliver_return_detail_ref_warehouse_record__main__reason__icontains=reason)
+    if name:
+        rows = rows.filter(deliver_return_detail_ref_warehouse_record__product_base__name__icontains=name)
+    if model:
+        rows = rows.filter(deliver_return_detail_ref_warehouse_record__product_base__name__icontains=model)
+    warehouses_ids = Warehouse.getManagerWarehouses(request.user)
+    department_ids = request.user.getSubDepartmentIds()
+    user_ids = request.user.getSubEmployeeIds()
+    rows = rows.filter(warehouse_id__in=warehouses_ids)
+    rows = rows.filter(
+        Q(deliver_return_detail_ref_warehouse_record__main__department_id__in=department_ids)
+        | Q(deliver_return_detail_ref_warehouse_record__main__create_user_id__in=user_ids)
+        | Q(deliver_return_detail_ref_warehouse_record__main__create_user=request.user))
+    return rows
+
+def get_deliver_return_query_data(rows):
+    rows = rows.values(
+        'id',
+        'type',
+        'deliver_return_detail_ref_warehouse_record__product_base__name',
+        'deliver_return_detail_ref_warehouse_record__product_base__model',
+        'deliver_return_detail_ref_warehouse_record__product_base__type',
+        'deliver_return_detail_ref_warehouse_record__product_base__warehouse_place',
+        'warehouse__name',
+        'cur_count',
+        'deliver_return_detail_ref_warehouse_record__return_count',
+        'deliver_return_detail_ref_warehouse_record__return_cost',
+        'deliver_return_detail_ref_warehouse_record__notes',
+        'deliver_return_detail_ref_warehouse_record__main__create_user__name',
+        'deliver_return_detail_ref_warehouse_record__main__create_time',
+        'deliver_return_detail_ref_warehouse_record__main__reason',
+        'deliver_return_detail_ref_warehouse_record__main__no',
+        'deliver_return_detail_ref_warehouse_record__deliver_detail__main__no',
+    )
+    data = []
+    for row in rows:
+        warehouse_record_type = WarehouseRecord.TYPE_CHOICES[row['type']][1]
+        product_type_text = ProductBase.TYPE_CHOICES[row['deliver_return_detail_ref_warehouse_record__product_base__type']][1]
+        item = {
+            'id': row['id'],
+            'type': warehouse_record_type,
+            'product_type': product_type_text,
+
+            'name': row['deliver_return_detail_ref_warehouse_record__product_base__name'],
+            'model': row['deliver_return_detail_ref_warehouse_record__product_base__model'],
+            'warehouse_place': row['deliver_return_detail_ref_warehouse_record__product_base__warehouse_place'],
+
+            'warehouse': row['warehouse__name'],
+            'cur_count': Formater.formatCountShow(row['cur_count']),
+
+            'create_user': row['deliver_return_detail_ref_warehouse_record__main__create_user__name'],
+            'create_time': Formater.formatStrTime(row['deliver_return_detail_ref_warehouse_record__main__create_time']),
+            'return_count': Formater.formatCountShow(row['deliver_return_detail_ref_warehouse_record__return_count']),
+            'return_cost': Formater.formatAmountShow(row['deliver_return_detail_ref_warehouse_record__return_cost']),
+            'reason': row['deliver_return_detail_ref_warehouse_record__main__reason'],
+            'notes': row['deliver_return_detail_ref_warehouse_record__notes'],
+            'return_no': row['deliver_return_detail_ref_warehouse_record__main__no'],
+
+            'no': row['deliver_return_detail_ref_warehouse_record__deliver_detail__main__no']
+        }
+        data.append(item)
+    return data
+
+def getReturnPermissionByType(type, action):
+    permissions = {
+        ProductBase.MATERIAL: {'view': 'warehouse.view_material_deliver_return_query',
+                               'export': 'warehouse.export_material_deliver_return_query',
+                               },
+        ProductBase.CONSUMABLE: {'view': 'purchase.view_consumable_deliver_return_query',
+                                 'export': 'purchase.export_consumable_deliver_return_query',
+                               }
+    }
+    return permissions[type][action]
+
+
+@csrf_exempt
+@token_required
+def stock_log(request):
+    keyword = request.GET.get('keyword')
+    action = request.GET.get('action')
+    happen_time = request.GET.get('happen_time')
+    warehouse_id = request.GET.get('warehouse_id')
+    product_id = request.GET.get('product_id')
+
+    product_base = ProductBase.getById(int(product_id))
+    valid_permission(request.user, WarehouseStock.getPermissionByType(product_base.type, 'log'))
+
+    page, page_size = utils.get_page_info(request)
+
+    product_id = int(product_id)
+    warehouse_id = int(warehouse_id)
+    where = ''
+
+    if happen_time:
+        happen_time = happen_time.split(' - ')
+        where += " AND m.happen_time >= '%s' " % happen_time[0]
+        where += " AND m.happen_time <= '%s' " % (happen_time[1] + ' 23:59:59')
+    if action:
+        where += ' AND m.type = %d' % int(action)
+
+    if keyword and len(keyword) > 0:
+        where += " AND (m.order_no like '%" + keyword + "%' " \
+                 " or m.warehouse_name like '%" + keyword + "%' )"
+
+    page_sql = ' LIMIT %d OFFSET %d ' % (page_size, page * page_size)
+    sql = """SELECT * FROM(
+    		  SELECT
+    			pwr.type,
+    			pwr.happen_time,
+    			(CASE WHEN pwr.type in (3, 4)  THEN gd.`no`
+    					WHEN pwr.type = 5 THEN iv.`no`
+    					WHEN pwr.type = 6 THEN gr.`no`
+    					WHEN pwr.type = 7 THEN gdr.`no`
+    					else '' END) AS order_no,
+    			(CASE WHEN pwr.type in (3, 4) THEN dd.total_cost
+    					WHEN pwr.type = 5 THEN ind.price
+    					WHEN pwr.type = 6 THEN gedr.price
+    					WHEN pwr.type = 7 THEN gdrd.return_cost
+    					else '' END) AS order_price,
+    			pwr.count,
+    			pwr.amount,
+    			pwr.amount/pwr.count as cost,
+    			pw.name as warehouse_name,
+    			pwr.warehouse_id AS warehouse_id,
+    			pwr.product_id AS product_id,
+    			pwr.id AS id,
+    			pwr.cur_count as cur_count,
+    			pwr.cur_amount as cur_amount
+    			FROM product_warehouse_record pwr LEFT JOIN
+    			product_warehouse pw ON pwr.warehouse_id = pw.id LEFT JOIN
+    			product_warehouse_stock pws ON pwr.product_id = pws.product_id AND pwr.warehouse_id=pws.warehouse_id LEFT JOIN
+    			deliver_detail dd ON pwr.id=dd.warehouse_record_id LEFT JOIN
+    			deliver gd ON dd.main_id=gd.id LEFT JOIN
+    			deliver_detail_return gdrd ON pwr.id = gdrd.warehouse_record_id LEFT JOIN
+    			deliver_return gdr ON gdrd.main_id = gdr.id LEFT JOIN
+    			godown_entry_detail_return gedr ON pwr.id=gedr.warehouse_record_id LEFT JOIN
+    			godown_entry_return gr ON gedr.main_id=gr.id LEFT JOIN
+    			inventory_detail ind on ind.warehouse_record_id=pwr.id left join 
+    			inventory iv ON iv.id=ind.main_id
+    			WHERE pwr.warehouse_id = %(warehose)d AND  pwr.product_id = %(product)d AND pwr.type >=3
+          UNION ALL
+    		SELECT
+    			pwr.type,
+    			pwr.happen_time,
+    			(CASE WHEN pwr.type in (0, 1) THEN ge.`no`
+    			            WHEN pwr.type = 2 THEN iv.`no`
+    						else '' END) AS order_no,
+    			(CASE WHEN pwr.type in (0, 1) THEN ged.price
+    			       WHEN pwr.type = 2 THEN ind.price
+    					else '' END) AS order_price,
+    			pwr.count,
+    			pwr.amount,
+    			pwr.amount/pwr.count as cost,
+    			pw.name as warehouse_name,
+    			pwr.warehouse_id AS warehouse_id,
+    			pwr.product_id AS product_id,
+    			pwr.id AS id,
+    			pwr.cur_count as cur_count,
+    			pwr.cur_amount as cur_amount
+    			FROM product_warehouse_record pwr LEFT JOIN
+    			product_warehouse pw ON pwr.warehouse_id = pw.id LEFT JOIN
+    			product_warehouse_stock pws ON pwr.product_id = pws.product_id AND pwr.warehouse_id=pws.warehouse_id LEFT JOIN
+    			product_warehouse_record_detail pwrd ON pwr.id=pwrd.warehouse_record_id LEFT JOIN
+    			godown_entry_detail ged ON pwrd.warehouse_stock_record_id=ged.stock_record_id LEFT JOIN
+    			godown_entry ge ON ged.main_id=ge.id LEFT JOIN
+    			inventory_detail ind on ind.warehouse_stock_record_id = pwrd.warehouse_stock_record_id left join 
+    			inventory iv ON iv.id = ind.main_id
+    			WHERE pwr.warehouse_id = %(warehose)d AND  pwr.product_id = %(product)d AND (pwr.type <= 2)
+    	        ) as m
+                WHERE 1=1 %(where)s
+                ORDER BY m.happen_time DESC
+                """ % {'warehose': warehouse_id, 'product': product_id, 'where': where}
+
+    sum_sql = """ SELECT
+                        COUNT(0)
+                        FROM
+                        (%s) AS t
+                        """ % sql
+    items = []
+    sql = '%s %s' % (sql, page_sql)
+    cursor = connection.cursor()
+    cursor.execute(sql)
+    row = cursor.fetchone()
+    total = 0
+    while row:
+        action_text = WarehouseRecord.TYPE_CHOICES[row[0]][1]
+        text = ''
+        if row[0] == WarehouseRecord.RK_ZJ:
+            text = u'入库单[' + row[2] + u']'
+        elif row[0] == WarehouseRecord.RK_CG:
+            text = u'采购转入库,入库单[' + row[2] + u']'
+        elif row[0] == WarehouseRecord.RK_PY:
+            text = u'盘盈单[' + row[2] + u']'
+        elif row[0] == WarehouseRecord.CK_ZJ:
+            text = u'出库单[' + row[2] + ']'
+        elif row[0] == WarehouseRecord.CK_XS:
+            text = u'订单转出库,出库单[' + row[2] + u']'
+        elif row[0] == WarehouseRecord.CK_PK:
+            text = u'盘亏单[' + row[2] + u']'
+        elif row[0] == WarehouseRecord.TH:
+            text = u'退货单[' + row[2] + u']'
+        elif row[0] == WarehouseRecord.TL:
+            text = u'退料单[' + row[2] + u']'
+
+        item = {
+            'action': row[0],
+            'action_text': action_text,
+            'create_time': strftime(row[1]),
+            'order_no': row[2],
+            'count': Formater.formatCountShow(row[4]),
+            'cost': Formater.formatPriceShow(row[6]),
+            'price': Formater.formatPriceShow(row[3]),
+            'warehouse_text': row[7],
+            'text': text,
+            'cur_count': Formater.formatCountShow(row[11]),
+            'cur_amount': Formater.formatAmountShow(row[12]),
+        }
+        items.append(item)
+        row = cursor.fetchone()
+
+    cursor.execute(sum_sql)
+    row = cursor.fetchone()
+    if row:
+        total = row[0]
+
+    return DataGridJSONResponse(items, total)

+ 0 - 0
apps/office/__init__.py


+ 18 - 0
apps/office/filters.py

@@ -0,0 +1,18 @@
+#coding=utf-8
+import django_filters
+from apps.base import clean_datetime_range
+from models import Notice
+
+
+class NoticeFilter(django_filters.FilterSet):
+    company = django_filters.CharFilter(name='department__name',lookup_expr='icontains')
+    title = django_filters.CharFilter(name='title', lookup_expr='icontains')
+    create_time = django_filters.DateTimeFromToRangeFilter(field_name='create_time')
+
+    class Meta:
+        model = Notice
+        fields = "__all__"
+
+    def __init__(self, data=None, *args, **kwargs):
+        data = clean_datetime_range(data, 'create_time')
+        super(NoticeFilter, self).__init__(data, *args, **kwargs)

+ 0 - 0
apps/office/migrations/__init__.py


+ 114 - 0
apps/office/models.py

@@ -0,0 +1,114 @@
+# coding=utf-8
+
+from django.db import models
+from django.conf import settings
+from django.utils import timezone
+from apps.account.models import Department, User
+from apps.exceptions import CustomError
+from libs.filefield import PathAndRename
+
+
+class Notice(models.Model):
+    NORMAL = 0
+    URGENT = 1
+    PRIORITY_CHOICES = (
+        (NORMAL, u'普通'),
+        (URGENT, u'紧急'),
+    )
+
+    department = models.ForeignKey(Department, verbose_name=u"部门", on_delete=models.PROTECT)
+    priority = models.PositiveSmallIntegerField(choices=PRIORITY_CHOICES, verbose_name=u"优先级", default=NORMAL)
+    title = models.CharField(max_length=100, verbose_name=u"标题")
+    content = models.CharField(max_length=1024, verbose_name=u"公告内容")
+    dendline = models.DateTimeField(verbose_name=u"截止时间")
+    company = models.ForeignKey(Department, verbose_name=u'公司', editable=False, related_name='notice_ref_company')
+    create_user = models.ForeignKey(User, editable=False, related_name='notice_ref_user', verbose_name=u"发布人", on_delete=models.PROTECT)
+    create_time = models.DateTimeField(verbose_name=u"发布时间", default=timezone.now)
+    hits = models.PositiveIntegerField(verbose_name=u"查看次数", default=0, editable=False)
+    create_user_department = models.ForeignKey(Department, verbose_name=u"创建人部门", null=True, blank=True, on_delete=models.PROTECT,
+                                   editable=False, related_name='notice_ref_create_user_department')
+
+    class Meta:
+        db_table = "office_notice"
+        verbose_name = u"信息管理"
+        ordering = ('-id',)
+        default_permissions = ()
+        permissions = (
+            ("view_notice", u"浏览"),
+            ("add_notice", u"添加"),
+            ("delete_notice", u"删除"),
+        )
+
+    @staticmethod
+    def getById(id):
+        instance = Notice.objects.filter(pk=id).first()
+        if not instance:
+            raise CustomError(u'未找到相应的公告')
+        return instance
+
+class NoticeBrowseRecord(models.Model):
+    notice = models.ForeignKey(Notice, verbose_name=u"通知")
+    browse_user = models.ForeignKey(User, editable=False,verbose_name=u"浏览人", on_delete=models.PROTECT)
+    department = models.ForeignKey(Department, verbose_name=u"浏览人部门", null=True, blank=True, on_delete=models.PROTECT,
+                                   editable=False, related_name='office_notice_browse_record_ref_department')
+    create_time = models.DateTimeField(verbose_name=u"浏览时间", default=timezone.now)
+
+    class Meta:
+        db_table = "office_notice_browse_record"
+        verbose_name = u"公告浏览记录"
+        ordering = ('-id',)
+        default_permissions = ()
+        permissions = (
+            ("view_browserecord", u"浏览"),
+        )
+
+    @staticmethod
+    def getByNotice(id):
+        instances = NoticeBrowseRecord.objects.filter(notice_id=id)
+        return instances
+
+    @staticmethod
+    def getById(id):
+        instance = NoticeBrowseRecord.objects.filter(pk=id).first()
+        if not instance:
+            raise CustomError(u'未找到相应的浏览记录')
+        return instance
+
+class NoticeReply(models.Model):
+    notice = models.ForeignKey(Notice, verbose_name=u"通知")
+    content = models.CharField(max_length=500, verbose_name=u"内容")
+    create_user = models.ForeignKey(User, editable=False, related_name='noticereply_ref_user', verbose_name=u"回复人", on_delete=models.PROTECT)
+    create_time = models.DateTimeField(verbose_name=u"回复时间", default=timezone.now)
+    department = models.ForeignKey(Department, verbose_name=u"回复人部门", null=True, blank=True, on_delete=models.PROTECT,
+                                   editable=False, related_name='office_notice_reply_ref_department')
+    class Meta:
+        db_table = "office_notice_reply"
+        verbose_name = u"公告回复"
+        ordering = ("-id",)
+
+    @staticmethod
+    def getByNotice(id):
+        instances = NoticeReply.objects.filter(notice_id=id)
+        return instances
+
+    @staticmethod
+    def getById(id):
+        instance = NoticeReply.objects.filter(pk=id).first()
+        if not instance:
+            raise CustomError(u'未找到相应的回复')
+        return instance
+
+class NoticeAttachment(models.Model):
+    notice = models.ForeignKey(Notice, verbose_name=u"通知")
+    file = models.CharField(max_length=100, verbose_name=u"文件")
+
+    class Meta:
+        db_table = "office_notice_attachment"
+        verbose_name = u"公告附件"
+
+    @staticmethod
+    def getByNotice(id):
+        instances = NoticeAttachment.objects.filter(notice_id=id)
+        return instances
+
+path_and_rename = PathAndRename("office_notice/files/")

+ 177 - 0
apps/office/serializers.py

@@ -0,0 +1,177 @@
+#coding=utf-8
+
+from rest_framework import serializers
+
+from apps import base
+from apps.account.models import User
+from apps.base import Formater
+from apps.exceptions import CustomError
+from apps.foundation.models import BizLog
+from apps.office.models import Notice, NoticeReply, NoticeBrowseRecord
+from apps.serializer_errors import dump_serializer_errors
+from libs.booleancharfield import CountShowCharField
+
+
+
+class NoticeSerializer(serializers.ModelSerializer):
+    department_name = serializers.CharField(source='department.name', read_only=True)
+    priority_text = serializers.CharField(source='get_priority_display', read_only=True)
+    dendline = serializers.DateTimeField(format=base.DATE_FORMAT)
+    company_name = serializers.CharField(source='company.name', read_only=True)
+    create_time = serializers.DateTimeField(format=base.DATETIME_FORMAT1, read_only=True)
+    create_user_text = serializers.CharField(source='create_user.name', read_only=True)
+    receiver_count = serializers.SerializerMethodField()
+    reply_count = serializers.SerializerMethodField()
+    null = serializers.SerializerMethodField()
+
+    class Meta:
+        model = Notice
+        fields = '__all__'
+
+    def get_receiver_count(self, obj):
+        receiver_count = User.objects.filter(department__lft__gte=obj.department.lft, department__rgt__lte=obj.department.rgt,status=User.INSERVICE).count()
+        return receiver_count
+
+    def get_reply_count(self, obj):
+        reply_count = NoticeReply.objects.filter(notice=obj).values_list('create_user', flat=True).distinct().count()
+        return reply_count
+
+    def get_null(self, obj):
+        receiver_count = User.objects.filter(department__lft__gte=obj.department.lft,
+                                             department__rgt__lte=obj.department.rgt, status=User.INSERVICE).count()
+        reply_count = NoticeReply.objects.filter(notice=obj).values_list('create_user', flat=True).distinct().count()
+        if receiver_count == 0:
+            null = '0%'
+        else:
+            null = str(round(reply_count * 1.0 / receiver_count * 100 * 100) / 100) + '%'
+        return null
+
+    @staticmethod
+    def factory(user, data, id=None):
+        if id:
+            instance = Notice.getById(id)
+        else:
+            instance = None
+        serializer = NoticeSerializer(instance, data=data)
+        serializer.user = user
+        return serializer
+
+    def create(self, validated_data):
+        validated_data['create_user'] = self.user
+        validated_data['create_user_department'] = self.user.department
+        validated_data['company'] = self.user.department.getCompany()
+        notice = Notice.objects.create(**validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加公告[%s],id=%d" % (notice.title, notice.id),
+            validated_data
+        )
+        return notice
+
+    def update(self, instance, validated_data):
+        instance = super(NoticeSerializer, self).update(instance, validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.UPDATE,
+            u"修改公告[%s],id=%d" % (instance.title, instance.id),
+            validated_data
+        )
+        return instance
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+class NoticeBrowseRecordSerializer(serializers.ModelSerializer):
+    browse_user_text = serializers.CharField(source='browse_user.name', read_only=True)
+    department_text = serializers.CharField(source='department.name', read_only=True)
+    class Meta:
+        model = NoticeBrowseRecord
+        fields = '__all__'
+
+    @staticmethod
+    def factory(user, data, id=None):
+        if id:
+            instance = NoticeBrowseRecord.getById(id)
+        else:
+            instance = None
+        serializer = NoticeBrowseRecordSerializer(instance, data=data)
+        serializer.user = user
+        return serializer
+
+    def create(self, validated_data):
+        validated_data['browse_user'] = self.user
+        validated_data['department'] = self.user.department
+        instance = NoticeBrowseRecord.objects.create(**validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加浏览记录[%s],id=%d" % (instance.notice.title, instance.id),
+            validated_data
+        )
+        return instance
+
+    def update(self, instance, validated_data):
+        instance = super(NoticeBrowseRecordSerializer, self).update(instance, validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.UPDATE,
+            u"修改浏览记录[%s],id=%d" % (instance.notice.title, instance.id),
+            validated_data
+        )
+        return instance
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+
+class NoticeReplySerializer(serializers.ModelSerializer):
+    create_user_text = serializers.CharField(source='create_user.name', read_only=True)
+    department_text = serializers.CharField(source='department.name', read_only=True)
+    class Meta:
+        model = NoticeReply
+        fields = '__all__'
+
+    @staticmethod
+    def factory(user, data, id=None):
+        if id:
+            instance = NoticeReply.getById(id)
+        else:
+            instance = None
+        serializer = NoticeReplySerializer(instance, data=data)
+        serializer.user = user
+        return serializer
+
+    def create(self, validated_data):
+        validated_data['create_user'] = self.user
+        validated_data['department'] = self.user.department
+        instance = NoticeReply.objects.create(**validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加回复[%s],id=%d" % (instance.notice.title, instance.id),
+            validated_data
+        )
+        return instance
+
+    def update(self, instance, validated_data):
+        instance = super(NoticeReplySerializer, self).update(instance, validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.UPDATE,
+            u"修改回复[%s],id=%d" % (instance.notice.title, instance.id),
+            validated_data
+        )
+        return instance
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))

+ 19 - 0
apps/office/urls.py

@@ -0,0 +1,19 @@
+#coding=utf-8
+
+from django.conf.urls import url
+
+from views import *
+
+urlpatterns = (
+    url(r'^notice/data/$', notice_list),
+    url(r'^notice/save/$', notice_save),
+    url(r'^notice/delete/$', notice_delete),
+    url(r'^notice/detail/$', notice_detail),
+    url(r'^notice/answer/$', notice_answer),
+    url(r'^notice/browserecord/$', notice_browserecord),
+    url(r'^notice/upload_files/$', notice_upload_files),
+    url(r'^notice/files/$', notice_files),
+    url(r'^notice/del_files/$', notice_del_files),
+    url(r'^notice/touch_notice_list/$', touch_notice_list),
+
+)

+ 303 - 0
apps/office/views.py

@@ -0,0 +1,303 @@
+# coding=utf-8
+import os
+import traceback
+import json
+
+from django.conf import settings
+from django.utils import timezone
+from django.db.models import ProtectedError
+from django.views.decorators.csrf import csrf_exempt
+from django.db import transaction, IntegrityError
+from django.db.models import Q
+
+from apps.office.filters import NoticeFilter
+from apps.office.models import Notice, NoticeReply, NoticeBrowseRecord, NoticeAttachment
+
+from apps.account.decorators import token_required, permission_required
+from apps.base import Formater
+from apps.foundation.models import BizLog
+from apps.office.serializers import NoticeSerializer, NoticeBrowseRecordSerializer, NoticeReplySerializer
+
+from libs.http import JSONResponse, JSONError, DataGridJSONResponse
+from libs import utils
+from apps.exceptions import CustomError
+
+
+@permission_required('office.view_notice')
+def notice_list(request):
+    f = NoticeFilter(request.GET, queryset=Notice.objects.filter())
+    rows, total = utils.get_page_data(request, f.qs)
+    serializer = NoticeSerializer(rows, many=True)
+    return DataGridJSONResponse(serializer.data, total)
+
+
+@csrf_exempt
+@permission_required('office.add_notice')
+def notice_save(request):
+    id = request.GET.get('id')
+    data = json.loads(request.body)
+    try:
+        with transaction.atomic():
+            data['dendline'] += ' 00:00:00'
+            serializer = NoticeSerializer.factory(request.user, data, id)
+            serializer = serializer.validSave()
+            if id:
+                item_data = {'notice': serializer.id}
+                replys = NoticeReply.getByNotice(id)
+                reply_ids = replys.values_list('id')
+                for reply_id in reply_ids:
+                    reply_serializer = NoticeReplySerializer.factory(request.user, item_data, reply_id[0])
+                    reply_serializer.validSave()
+
+                browserecords = NoticeBrowseRecord.getByNotice(id)
+                browserecord_ids = browserecords.values_list('id')
+                for browserecord_id in browserecord_ids:
+                    browserecord_serializer = NoticeBrowseRecordSerializer.factory(request.user, item_data, browserecord_id[0])
+                    browserecord_serializer.validSave()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'保存失败!')
+    return JSONResponse()
+
+@permission_required('office.delete_notice')
+def notice_delete(request):
+    id = int(request.GET.get('id'))
+
+    try:
+        with transaction.atomic():
+            notice = Notice.getById(id)
+            BizLog.objects.addnew(request.user, BizLog.DELETE, u"删除公告[%s],id=%d" % (notice.title, notice.id))
+            notice.delete()
+            browserecords = NoticeBrowseRecord.getByNotice(id)
+            for browserecord in browserecords:
+                BizLog.objects.addnew(request.user, BizLog.DELETE, u"删除公告回复id=%d" % (browserecord.id))
+                browserecord.delete()
+            replys = NoticeReply.getByNotice(id)
+            for reply in replys:
+                BizLog.objects.addnew(request.user, BizLog.DELETE, u"删除公告回复id=%d" % (reply.id))
+                reply.delete()
+            attachments = NoticeAttachment.getByNotice(id)
+            for attachment in attachments:
+                BizLog.objects.addnew(request.user, BizLog.DELETE, u"删除公告回复id=%d" % (attachment.id))
+                attachment.delete()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except ProtectedError:
+        return JSONError(u'该公告已被使用,禁止删除!')
+    except IntegrityError:
+        return JSONError(u'该公告已被使用,禁止删除!')
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'删除失败!')
+
+    return JSONResponse({})
+
+@token_required
+@permission_required('office.view_notice')
+def notice_detail(request):
+    id = request.GET.get('id')
+    if not id:
+        return JSONResponse()
+    instance = Notice.getById(id)
+
+    try:
+        with transaction.atomic():
+            hits = instance.hits
+            new_hits = int(hits) + 1
+            instance.hits = new_hits
+            instance.save()
+            item_data = {'notice': instance.id}
+            browserecord_serializer = NoticeBrowseRecordSerializer.factory(request.user, item_data)
+            browserecord_serializer.validSave()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'查看失败!')
+
+    if instance.priority == Notice.NORMAL:
+        priority = u'普通'
+    else:
+        priority = u'紧急'
+    main_data = {
+        'id': id,
+        'title': instance.title,
+        'priority': instance.priority,
+        'priority_text': priority,
+        'department_name': instance.department.name,
+        'hits': instance.hits,
+        'content': instance.content,
+        'create_user_text': instance.create_user.name,
+        'create_time': Formater.formatStrTime(instance.create_time),
+        'dendline': Formater.formatStrDate(instance.dendline)
+    }
+
+    data = {
+        'main_data': main_data,
+        'items_data': []
+    }
+    rows = NoticeReply.getByNotice(id)
+    for row in rows:
+        item_data = {
+            'id': row.id,
+            'content': row.content,
+            'create_user_text': row.create_user.name,
+            'create_time': Formater.formatStrTime(row.create_time)
+        }
+        data['items_data'].append(item_data)
+
+    return JSONResponse(data)
+
+@token_required
+def notice_answer(request):
+    id = request.GET.get('id')
+    data = json.loads(request.body)
+    try:
+        with transaction.atomic():
+            data['notice'] = id
+            reply_serializer = NoticeReplySerializer.factory(request.user, data)
+            reply_serializer.validSave()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'保存失败!')
+    return JSONResponse()
+
+
+@token_required
+@permission_required('office.view_browserecord')
+def notice_browserecord(request):
+    id = request.GET.get('id')
+    if not id:
+        return JSONResponse()
+    instance = Notice.getById(id)
+    if instance.priority == Notice.NORMAL:
+        priority = u'普通'
+    else:
+        priority = u'紧急'
+    main_data = {
+        'id': id,
+        'title': instance.title,
+        'priority': instance.priority,
+        'priority_text': priority,
+        'department_name': instance.department.name,
+        'hits': Formater.formatCountShow(instance.hits),
+        'content': instance.content,
+        'create_time': Formater.formatStrTime(instance.create_time),
+        'dendline': Formater.formatStrDate(instance.dendline)
+    }
+
+    data = {
+        'main_data': main_data,
+        'items_data': []
+    }
+    rows = NoticeBrowseRecord.getByNotice(id)
+    for row in rows:
+        item_data = {
+            'id': row.id,
+            'department': row.department.name,
+            'browse_user_name': row.browse_user.name,
+            'create_time': Formater.formatStrTime(row.create_time)
+        }
+        data['items_data'].append(item_data)
+
+    return JSONResponse(data)
+
+
+@token_required
+def notice_upload_files(request):
+    ids = request.POST.get('ids')
+    files = request.FILES.getlist('file')
+    try:
+        with transaction.atomic():
+            ids = str(ids).split(',')
+            from models import path_and_rename
+            for id in ids:
+                notice = Notice.objects.filter(id=id).first()
+                if not notice:
+                    raise CustomError(u'未找到公告')
+                for file in files:
+                    filename = "%s%d-%s.%s" % (
+                        path_and_rename.path,
+                        notice.id,
+                        timezone.now().strftime('%Y%m%d%H%M%S%f'),
+                        file.name.split('.')[-1]
+                    )
+                    filename = filename.lower()
+                    full_filename = "%s/%s" % (settings.MEDIA_ROOT, filename)
+                    with open(full_filename, 'wb+') as destination:
+                        for chunk in file.chunks():
+                            destination.write(chunk)
+
+                    NoticeAttachment.objects.create(notice=notice, file=filename)
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception,e:
+        traceback.print_exc()
+        return JSONError(u'上传失败')
+    return JSONResponse({})
+
+@token_required
+def notice_files(request):
+    id = request.GET.get('id')
+    rows = NoticeAttachment.objects.filter(notice_id=int(id))
+    rows, total = utils.get_page_data(request, rows)
+    data = []
+    for row in rows:
+        item = {
+            'id': row.id,
+            'notice_id': row.notice.id,
+            'files': unicode(row.file),
+            'files_name': str(row.file).split('/')[-1],
+            'files_url': row.file,
+        }
+        data.append(item)
+    return DataGridJSONResponse(data, total)
+
+@token_required
+def notice_del_files(request):
+    id = request.GET.get('id')
+
+    try:
+        with transaction.atomic():
+            file = NoticeAttachment.objects.filter(id=int(id)).first()
+            file.delete()
+            try:
+                os.remove("%s/%s" % (settings.MEDIA_ROOT, file.file))
+            except:
+                pass
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except:
+        traceback.print_exc()
+        return JSONError(u"删除失败")
+    return JSONResponse({})
+
+@csrf_exempt
+@permission_required('office.view_notice')
+def touch_notice_list(request):
+
+    children = request.user.getSubDepartmentIds()
+    children.append(request.user.department.id)
+    rows = Notice.objects.filter(dendline__gte=timezone.now(), department__in=children)
+    browsed_ids = NoticeBrowseRecord.objects.filter(browse_user_id=request.user).values_list('notice_id', flat=True)
+    not_notices = rows.filter(~Q(id__in=browsed_ids))
+
+    ids = []
+    for notice in not_notices:
+        ids.append(notice.id)
+    rows, total = utils.get_page_data(request, rows)
+
+    data = []
+    for row in rows:
+        item = {'title': row.title, 'id': row.id, 'create_time': Formater.formatStrTime(row.create_time), 'status_text': u'已读',
+                'status': 2}
+        if row.id in ids:
+            item['status_text'] = u'未读'
+            item['status'] = 1
+        data.append(item)
+
+    return  DataGridJSONResponse(data, total)

+ 0 - 0
apps/order/__init__.py


+ 46 - 0
apps/order/filters.py

@@ -0,0 +1,46 @@
+# coding=utf-8
+import django_filters
+from apps.base import clean_datetime_range
+from models import SaleOrder, GoodsDeliver
+
+
+class SaleOrderFilter(django_filters.FilterSet):
+    create_time = django_filters.DateTimeFromToRangeFilter(field_name='create_time')
+    no = django_filters.CharFilter(name='no', lookup_expr='icontains')
+    create_user = django_filters.CharFilter(name='create_user__name', lookup_expr='icontains')
+    customer_name = django_filters.CharFilter(name='customer__name', lookup_expr='icontains')
+    customer_tel = django_filters.CharFilter(name='customer__mobile', lookup_expr='icontains')
+
+    class Meta:
+        model = SaleOrder
+        fields = (
+            'create_time', 'no', 'customer_name', 'customer_tel',
+            'status', 'create_user'
+        )
+
+    def __init__(self, data=None, *args, **kwargs):
+        data = clean_datetime_range(data, 'create_time', 'source')
+        super(SaleOrderFilter, self).__init__(data, *args, **kwargs)
+
+
+class GoodsDeliverFilter(django_filters.FilterSet):
+    no = django_filters.CharFilter(name='no', lookup_expr='icontains')
+    create_user_text = django_filters.CharFilter(name='create_user__name', lookup_expr='icontains')
+    create_time = django_filters.DateTimeFromToRangeFilter(field_name='create_time')
+
+    class Meta:
+        model = GoodsDeliver
+        fields = "__all__"
+
+    def __init__(self, data=None, *args, **kwargs):
+        data = clean_datetime_range(data, 'create_time', 'source')
+        super(GoodsDeliverFilter, self).__init__(data, *args, **kwargs)
+
+class GoodsDeliverReturnFilter(django_filters.FilterSet):
+    no = django_filters.CharFilter(name='no', lookup_expr='icontains')
+
+    class Meta:
+        model = GoodsDeliver
+        fields = (
+            'no',
+        )

+ 0 - 0
apps/order/migrations/__init__.py


+ 309 - 0
apps/order/models.py

@@ -0,0 +1,309 @@
+# coding=utf-8
+from django.utils import timezone
+from apps.foundation.models import Option
+from apps.customer.models import Customer
+from apps.exceptions import CustomError
+from django.db import models
+from django.conf import settings
+from django.db.models import Sum
+
+from apps.account.models import User, Department
+from apps.goods.models import Goods
+from apps.warehouse.models import Warehouse, WarehouseStock, WarehouseRecord, WarehouseStockRecord
+
+
+class SaleOrder(models.Model):
+    no = models.CharField(max_length=20, verbose_name=u"单号", editable=False)
+    customer = models.ForeignKey(Customer, verbose_name=u"客户", on_delete=models.PROTECT)
+    status = models.PositiveSmallIntegerField(choices=settings.CHECK_STATUS_CHOICES, default=settings.DEFAULT, verbose_name=u"状态")
+    notes = models.CharField(max_length=200, verbose_name=u"备注", blank=True, null=True)
+    create_time = models.DateTimeField(verbose_name=u"创建时间", default=timezone.now, blank=True)
+    create_user = models.ForeignKey(User, related_name='sale_order_ref_create_user', verbose_name=u"创建人", on_delete=models.PROTECT, editable=False)
+    department = models.ForeignKey(Department, verbose_name=u"创建部门", editable=False, on_delete=models.PROTECT)
+    check_time = models.DateTimeField(verbose_name=u"审核时间", null=True)
+    check_user = models.ForeignKey(User, related_name='sale_order_ref_check_user', verbose_name=u"审核人", on_delete=models.PROTECT, null=True)
+    count = models.BigIntegerField(u'数量', default=0)
+    amount = models.BigIntegerField(u'金额', default=0)
+
+    class Meta:
+        db_table = "sale_order"
+        verbose_name = u"订单管理"
+        ordering = ('-id',)
+        index_together = (
+            'create_time', 'check_time', 'status'
+        )
+        unique_together = (
+           'no',
+        )
+        default_permissions = ()
+        permissions = ( # 销售订单管理
+            ("view_sale_order", u"浏览"),
+            ("add_sale_order", u"添加"),
+            ("check_sale_order", u"审核"),
+            ("delete_sale_order", u"删除"),
+            ("export_sale_order", u"导出"),
+            ("print_sale_order", u"打印"),
+        )
+
+    @staticmethod
+    def getById(id):
+        instance = SaleOrder.objects.filter(pk=id).first()
+        if not instance:
+            raise CustomError(u'未找到相应的订单')
+        return instance
+
+    def updateAmount(self):
+        sum_count = 0
+        sum_amount = 0
+
+        sum_row = SaleOrderDetail.objects.filter(main=self).aggregate(sum_count=Sum('count'), sum_amount=Sum('amount'))
+        if sum_row:
+            sum_count = sum_row['sum_count'] or 0
+            sum_amount = sum_row['sum_amount'] or 0
+
+        self.count = sum_count
+        self.amount = sum_amount
+        self.save()
+
+    def save(self, *args, **kwargs):
+        if self.no == None or self.no == '':
+            now = timezone.now()
+            rows = SaleOrder.objects.filter(create_time__gte=now.strftime('%Y-%m-%d')).order_by('-no')
+            count = rows.count()
+            if count == 0:
+                self.no = 'GS%s%03d' % (now.strftime('%Y%m%d'), count + 1)
+            else:
+                self.no = rows[0].no[:2] + str(int(rows[0].no[2:]) + 1)
+        super(SaleOrder, self).save(*args, **kwargs)
+
+
+class SaleOrderDetail(models.Model):
+    main = models.ForeignKey(SaleOrder, verbose_name=u'销售单', on_delete=models.PROTECT)
+    goods = models.ForeignKey(Goods, verbose_name=u'产品', on_delete=models.PROTECT)
+    quality_request = models.ForeignKey(Option, verbose_name=u"质量标准", null=True, blank=True, on_delete=models.PROTECT)
+    count = models.BigIntegerField(u'数量')
+    price = models.BigIntegerField(u'单价')
+    amount = models.BigIntegerField(u'金额', editable=False)
+
+    class Meta:
+        db_table = "sale_order_detail"
+        verbose_name = u"入库查询"
+        ordering = ('-id',)
+        default_permissions = ()
+        permissions = (# 销售订单明细
+            ("view_goods_godownentry_query", u"浏览"),
+            ("export_goods_godownentry_query", u"导出"),
+            ("print_goods_godownentry_query", u"打印"),
+        )
+
+class GoodsDeliver(models.Model):
+    no = models.CharField(max_length=20, verbose_name=u"出库单号", editable=False)
+    create_time = models.DateTimeField(verbose_name=u"创建时间", default=timezone.now)
+    create_user = models.ForeignKey(User, verbose_name=u"创建人", related_name='goodsdeliver_ref_create_user',
+                                    on_delete=models.PROTECT, blank=True, editable=False)
+    customer = models.ForeignKey(Customer, verbose_name=u"客户", on_delete=models.PROTECT, blank=True, null=True)
+    department = models.ForeignKey(Department, verbose_name=u"创建部门", related_name='goodsdeliver_ref_department',on_delete=models.PROTECT, blank=True, editable=False)
+    status = models.PositiveSmallIntegerField(choices=settings.CHECK_STATUS_CHOICES, verbose_name=u"审核状态",default=settings.DEFAULT)
+    check_user = models.ForeignKey(User, related_name='goodsdeliver_ref_check_user', verbose_name=u"审核人",on_delete=models.PROTECT, blank=True, null=True)
+    check_time = models.DateTimeField(verbose_name=u"审核时间", blank=True, null=True)
+    agent_user = models.ForeignKey(User, verbose_name=u"经办人", related_name='goodsdeliver_ref_agent_user', on_delete=models.PROTECT)
+    agent_department = models.ForeignKey(Department, verbose_name=u"经办人部门", related_name='goodsdeliver_ref_agent_department', on_delete=models.PROTECT, blank=True, editable=False)
+    warehouse = models.ForeignKey(Warehouse, verbose_name=u'仓别', on_delete=models.PROTECT)
+    sale_order = models.ForeignKey(SaleOrder, verbose_name=u'销售订单', on_delete=models.PROTECT, blank=True, null=True)
+    notes = models.CharField(max_length=200, verbose_name=u"备注", blank=True, null=True)
+
+    total_count = models.BigIntegerField(verbose_name=u'合计数量', default=0)
+    total_cost = models.BigIntegerField(verbose_name=u'合计成本', default=0)
+    total_amount = models.BigIntegerField(verbose_name=u'合计销售价', default=0, blank=True)
+    return_count = models.BigIntegerField(verbose_name=u"合计退库数量", default=0)
+    return_cost = models.BigIntegerField(verbose_name=u"合计退库成本", default=0)
+
+    class Meta:
+        db_table = "goods_deliver"
+        verbose_name = u"出库管理"
+        ordering = ('-id',)
+        index_together = (
+            'create_time', 'check_time', 'status'
+        )
+        unique_together = (
+            'no',
+        )
+        default_permissions = ()
+        permissions = ( # 成品出库管理
+            ("view_goods_deliver", u"浏览"),
+            ("add_goods_deliver", u"添加"),
+            ("check_goods_deliver", u"审核"),
+            ("delete_goods_deliver", u"删除"),
+            ("export_goods_deliver", u"导出"),
+            ("print_goods_deliver", u"打印"),
+        )
+
+    @staticmethod
+    def getById(id):
+        instances = GoodsDeliver.objects.filter(id=id).first()
+        if not instances:
+            raise CustomError(u'未找到相应的出库单')
+        return instances
+
+    def save(self, *args, **kwargs):
+        if self.no == None or self.no == '':
+            now = timezone.now()
+            rows = GoodsDeliver.objects.filter(create_time__gte=now.strftime('%Y-%m-%d')).order_by('-no')
+            count = rows.count()
+            if count == 0:
+                self.no = 'GC%s%03d' % (now.strftime('%Y%m%d'), count + 1)
+            else:
+                self.no = rows[0].no[:2] + str(int(rows[0].no[2:]) + 1)
+        super(GoodsDeliver, self).save(*args, **kwargs)
+
+    def update_total(self):
+        total_count = 0
+        total_cost = 0
+        total_amount = 0
+        sum_row = GoodsDeliverDetail.objects.filter(main=self).aggregate(total_count=Sum('count'), total_cost=Sum('total_cost'), total_amount=Sum('total_amount'))
+        if sum_row:
+            total_count = sum_row['total_count'] or 0
+            total_cost = sum_row['total_cost'] or 0
+            total_amount = sum_row['total_amount'] or 0
+        self.total_count = total_count
+        self.total_cost = total_cost
+        self.total_amount = total_amount
+
+        self.save()
+
+    def update_return(self):
+        return_count = 0
+        return_cost = 0
+        sum_row = GoodsDeliverDetail.objects.filter(main=self).aggregate(return_count=Sum('return_count'),
+                                                                    return_cost=Sum('return_cost'))
+        if sum_row:
+            return_count = sum_row['return_count'] or 0
+            return_cost = sum_row['return_cost'] or 0
+        self.return_count = return_count
+        self.return_cost = return_cost
+        self.save()
+
+
+class GoodsDeliverDetail(models.Model):
+    main = models.ForeignKey(GoodsDeliver, verbose_name=u'出库单', on_delete=models.PROTECT, related_name='goods_deliver_details')
+    product_base = models.ForeignKey(Goods, verbose_name=u'产品', on_delete=models.PROTECT)
+    count = models.BigIntegerField(verbose_name=u'数量', default=0)
+    price = models.BigIntegerField(verbose_name=u'单价', default=0)
+    warehouse_stock = models.ForeignKey(WarehouseStock, verbose_name=u'仓别库存', editable=False, on_delete=models.PROTECT)
+    warehouse_record = models.ForeignKey(WarehouseRecord, verbose_name=u'库存记录', on_delete=models.PROTECT, null=True,
+                                         blank=True, related_name='goods_deliver_detail_ref_warehouse_record')
+    warehouse_stockrecord = models.ForeignKey(WarehouseStockRecord, verbose_name=u'入库库存记录', on_delete=models.PROTECT,
+                                              null=True, blank=True,
+                                              related_name='goods_deliver_detail_ref_warehouse_stock_record')
+
+    total_cost = models.BigIntegerField(verbose_name=u'成本合计', default=0)
+    total_amount = models.BigIntegerField(verbose_name=u'销售价合计', default=0, blank=True)
+    return_count = models.BigIntegerField(verbose_name=u'退库数量', default=0)
+    return_cost = models.BigIntegerField(verbose_name=u'退库成本合计', default=0)
+    notes = models.CharField(max_length=200, verbose_name=u"备注", blank=True, null=True)
+
+    class Meta:
+        db_table = "goods_deliver_detail"
+        verbose_name = u"出库查询"
+        ordering = ('-id',)
+        default_permissions = ()
+        permissions = (# 成品出库管理明细
+            ("view_goods_deliver_query", u"浏览"),
+            ("export_goods_deliver_query", u"导出"),
+            ("print_goods_deliver_query", u"打印"),
+        )
+
+    def updateReturn(self):
+        return_count = 0
+        return_cost = 0
+        sum_row = GoodsDeliverReturnDetail.objects.filter(deliver_detail=self).aggregate(return_count=Sum('return_count'),
+                                                                                         return_cost=Sum('return_cost'))
+        if sum_row:
+            return_count = sum_row['return_count'] or 0
+            return_cost = sum_row['return_cost'] or 0
+        self.return_count = return_count
+        self.return_cost = return_cost
+        self.save()
+
+
+class GoodsDeliverReturn(models.Model):
+    no = models.CharField(max_length=20, verbose_name=u"退库单号", editable=False)
+    reason = models.CharField(max_length=500, verbose_name=u"原因", blank=True, null=True)
+    create_time = models.DateTimeField(verbose_name=u"创建时间", default=timezone.now)
+    create_user = models.ForeignKey(User, verbose_name=u"创建人", on_delete=models.PROTECT, blank=True, editable=False)
+    department = models.ForeignKey(Department, verbose_name=u"创建部门", on_delete=models.PROTECT, blank=True, editable=False)
+    return_count = models.BigIntegerField(verbose_name=u'退库合计数量', default=0)
+    return_cost = models.BigIntegerField(verbose_name=u'退库合计成本', default=0)
+
+    class Meta:
+        db_table = "goods_deliver_return"
+        verbose_name = u"退库管理"
+        ordering = ('-id',)
+        index_together = (
+            'create_time',
+        )
+        unique_together = (
+            'no',
+        )
+        default_permissions = ()
+        permissions = (
+            ("view_goods_deliver_return", u"浏览"),
+            ("add_goods_deliver_return", u"添加"),
+            ("print_goods_deliver_return", u"打印"),
+        )
+
+    @staticmethod
+    def getByIds(ids):
+        return GoodsDeliverReturn.objects.filter(id__in=ids)
+
+    @staticmethod
+    def getById(id):
+        instances = GoodsDeliverReturn.objects.filter(id=id).first()
+        if not instances:
+            raise CustomError(u'未找到相应的退库单')
+        return instances
+
+    def save(self, *args, **kwargs):
+        if self.no == None or self.no == '':
+            now = timezone.now()
+            rows = GoodsDeliverReturn.objects.filter(create_time__gte=now.strftime('%Y-%m-%d')).order_by('-no')
+            count = rows.count()
+            if count == 0:
+                self.no = 'TK%s%03d' % (now.strftime('%Y%m%d'), count + 1)
+            else:
+                self.no = rows[0].no[:2] + str(int(rows[0].no[2:]) + 1)
+        super(GoodsDeliverReturn, self).save(*args, **kwargs)
+
+    def update_total(self):
+        return_count = 0
+        return_cost = 0
+        sum_row = GoodsDeliverReturnDetail.objects.filter(main=self).aggregate(return_count=Sum('return_count'),
+                                                                               return_cost=Sum('return_cost'))
+        if sum_row:
+            return_count = sum_row['return_count'] or 0
+            return_cost = sum_row['return_cost'] or 0
+        self.return_count = return_count
+        self.return_cost = return_cost
+        self.save()
+
+
+class GoodsDeliverReturnDetail(models.Model):
+    main = models.ForeignKey(GoodsDeliverReturn, verbose_name=u'退库单', on_delete=models.PROTECT, related_name='goods_deliver_return_details')
+    deliver_detail = models.ForeignKey(GoodsDeliverDetail, verbose_name=u'出库明细', on_delete=models.PROTECT)
+    product_base = models.ForeignKey(Goods, verbose_name=u'产品', on_delete=models.PROTECT)
+    return_count = models.BigIntegerField(verbose_name=u'退库数量', default=0)
+    return_cost = models.BigIntegerField(verbose_name=u'退库成本合计', default=0)
+    warehouse_stock = models.ForeignKey(WarehouseStock, verbose_name=u'仓别库存', on_delete=models.PROTECT, editable=False)
+    warehouse_record = models.ForeignKey(WarehouseRecord, verbose_name=u'库存记录', on_delete=models.PROTECT, null=True,
+                                         blank=True, related_name='goods_return_deliver_detail_ref_warehouse_record')
+    notes = models.CharField(max_length=200, verbose_name=u"备注", blank=True, null=True)
+
+    class Meta:
+        db_table = "goods_deliver_detail_return"
+        verbose_name = u"退库查询"
+        ordering = ('-id',)
+        default_permissions = ()
+        permissions = (# 退库管理明细
+            ("view_goods_deliver_return_query", u"浏览"),
+            ("export_goods_deliver_return_query", u"导出"),
+        )

+ 192 - 0
apps/order/resources.py

@@ -0,0 +1,192 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import
+from import_export import resources
+from import_export.fields import Field
+
+class SaleOrderResource(resources.Resource):
+    def __init__(self):
+        super(SaleOrderResource, self).__init__()
+        self.fields['no'] = Field(attribute='no')
+        self.fields['create_time'] = Field(attribute='create_time')
+        self.fields['create_user_text'] = Field(attribute='create_user_text')
+        self.fields['customer_name'] = Field(attribute='customer_name')
+        self.fields['customer_tel'] = Field(attribute='customer_tel')
+        self.fields['products'] = Field(attribute='products')
+        self.fields['check_status_text'] = Field(attribute='check_status_text')
+        self.fields['check_user_text'] = Field(attribute='check_user_text')
+        self.fields['check_time'] = Field(attribute='check_time')
+        self.fields['notes'] = Field(attribute='notes')
+        self.fields['count'] = Field(attribute='count')
+        self.fields['amount'] = Field(attribute='amount')
+
+    def get_export_headers(self):
+        return [u'单号', u'客户', u'客户电话', u'产品', u'合计数量', u'合计金额', u'创建时间', u'创建人', u'审核状态', u'审核时间', u'审核人', u'备注']
+
+    class Meta:
+        fields = ('no', 'customer_name', 'customer_tel', 'products', 'count', 'amount', 'create_time', 'create_user_text', 'check_status_text',
+                  'check_time', 'check_user_text', 'notes',)
+        export_order = fields
+
+
+class SaleOrderDetailResource(resources.Resource):
+    def __init__(self):
+        super(SaleOrderDetailResource, self).__init__()
+        self.fields['name'] = Field(attribute='name')
+        self.fields['model'] = Field(attribute='model')
+        self.fields['quality_request_text'] = Field(attribute='quality_request_text')
+        self.fields['count'] = Field(attribute='count')
+        self.fields['price'] = Field(attribute='price')
+        self.fields['amount'] = Field(attribute='amount')
+
+    def get_export_headers(self):
+        return [u'产品名称', u'产品代码', u'质量标准',u'数量', u'单价', u'金额',]
+
+    class Meta:
+        fields = ('name', 'model', 'quality_request_text', 'count', 'price', 'amount',)
+        export_order = fields
+
+
+class GoodsDeliverResource(resources.Resource):
+
+    def __init__(self, is_show_cost=True):
+        super(GoodsDeliverResource, self).__init__()
+        self.is_show_cost = is_show_cost
+        self.fields['no'] = Field(attribute='no')
+        self.fields['sale_order_no'] = Field(attribute='sale_order_no')
+        self.fields['agent_user_text'] = Field(attribute='agent_user_text')
+        self.fields['agent_department_text'] = Field(attribute='agent_department_text')
+        self.fields['customer_name'] = Field(attribute='customer_name')
+        self.fields['customer_tel'] = Field(attribute='customer_tel')
+        self.fields['status_text'] = Field(attribute='status_text')
+        self.fields['create_user_text'] = Field(attribute='create_user_text')
+        self.fields['create_time'] = Field(attribute='create_time')
+        self.fields['check_user_text'] = Field(attribute='check_user_text')
+        self.fields['check_time'] = Field(attribute='check_time')
+        self.fields['warehouse_text'] = Field(attribute='warehouse_text')
+        self.fields['total_count'] = Field(attribute='total_count')
+
+        self.fields['total_amount'] = Field(attribute='total_amount')
+        self.fields['notes'] = Field(attribute='notes')
+        if is_show_cost:
+            self.fields['total_cost'] = Field(attribute='total_cost')
+            fields = ('no', 'sale_order_no', 'customer_name', 'customer_tel', 'agent_user_text','agent_department_text', 'total_count', 'total_cost', 'total_amount',
+                      'warehouse_text', 'create_time', 'create_user_text', 'status_text', 'check_time', 'check_user_text', 'notes')
+        else:
+            fields = ('no', 'sale_order_no', 'customer_name', 'customer_tel', 'agent_user_text','agent_department_text', 'total_count', 'total_amount',
+                      'warehouse_text', 'create_time', 'create_user_text', 'status_text', 'check_time', 'check_user_text', 'notes')
+        self._meta.export_order = fields
+
+
+    def get_export_headers(self):
+        if self.is_show_cost:
+            return [u'出库单号', u'销售单号', u'客户姓名', u'客户电话', u'经办人',u'经办部门', u'合计数量', u'合计成本', u'合计销售价', u'仓别',  u'创建时间', u'创建人', u'审核状态', u'审核时间', u'审核人', u'备注']
+        else:
+            return [u'出库单号', u'销售单号', u'客户姓名', u'客户电话', u'经办人',u'经办部门', u'合计数量', u'合计销售价', u'仓别',  u'创建时间', u'创建人', u'审核状态', u'审核时间', u'审核人', u'备注']
+
+
+class GoodsDeliverDetailResource(resources.Resource):
+
+    def __init__(self, is_show_cost=True):
+        super(GoodsDeliverDetailResource, self).__init__()
+        self.is_show_cost = is_show_cost
+        self.fields['name'] = Field(attribute='name')
+        self.fields['model'] = Field(attribute='model')
+        self.fields['unit_text'] = Field(attribute='unit_text')
+        self.fields['count'] = Field(attribute='count')
+        self.fields['price'] = Field(attribute='price')
+        self.fields['no'] = Field(attribute='no')
+        self.fields['warehouse_stock_count'] = Field(attribute='warehouse_stock_count')
+        self.fields['total_amount'] = Field(attribute='total_amount')
+        self.fields['notes'] = Field(attribute='notes')
+        if is_show_cost:
+            self.fields['total_cost'] = Field(attribute='total_cost')
+            fields = ('name', 'model', 'unit_text', 'no', 'count', 'price', 'warehouse_stock_count', 'total_cost', 'total_amount', 'notes')
+        else:
+            fields = ('name', 'model', 'unit_text', 'no', 'count', 'price', 'warehouse_stock_count', 'total_amount', 'notes')
+        self._meta.export_order = fields
+
+
+    def get_export_headers(self):
+        if self.is_show_cost:
+            return [u'名称', u'代码', u'单位', u'入库单号', u'数量', u'单价', u'仓别库存', u'成本合计', u'销售价合计', u'备注']
+        return [u'名称', u'代码', u'单位', u'入库单号', u'数量', u'单价', u'仓别库存', u'销售价合计', u'备注']
+
+
+class GoodsDeliverQueryResource(resources.Resource):
+    def __init__(self, is_show_cost=True):
+        super(GoodsDeliverQueryResource, self).__init__()
+        self.is_show_cost = is_show_cost
+        self.fields['no'] = Field(attribute='no')
+        self.fields['type'] = Field(attribute='type')
+        self.fields['name'] = Field(attribute='name')
+        self.fields['model'] = Field(attribute='model')
+        self.fields['unit'] = Field(attribute='unit')
+        self.fields['entry_no'] = Field(attribute='entry_no')
+        self.fields['product_type'] = Field(attribute='product_type')
+        self.fields['warehouse'] = Field(attribute='warehouse')
+        self.fields['cur_count'] = Field(attribute='cur_count')
+        self.fields['count'] = Field(attribute='count')
+        self.fields['total_amount'] = Field(attribute='total_amount')
+        self.fields['return_count'] = Field(attribute='return_count')
+        self.fields['check_user'] = Field(attribute='check_user')
+        self.fields['check_time'] = Field(attribute='check_time')
+        self.fields['warehouse_place'] = Field(attribute='warehouse_place')
+        self.fields['notes'] = Field(attribute='notes')
+        if is_show_cost:
+            self.fields['price'] = Field(attribute='price')
+            self.fields['total_cost'] = Field(attribute='total_cost')
+            self.fields['return_cost'] = Field(attribute='return_cost')
+            fields = ('no', 'type', 'name', 'model', 'unit', 'entry_no', 'product_type', 'warehouse',  'count', 'price',
+                      'cur_count', 'total_cost', 'total_amount', 'return_count', 'return_cost', 'check_time','check_user',
+                      'warehouse_place', 'notes')
+        else:
+            fields = ('no', 'type', 'name', 'model', 'unit', 'entry_no', 'product_type', 'warehouse',  'count',
+                      'cur_count', 'total_amount', 'return_count', 'check_time','check_user',
+                      'warehouse_place', 'notes')
+        self._meta.export_order = fields
+
+    def get_export_headers(self):
+        if self.is_show_cost:
+            return [u'出库单号', u'出库类别', u'产品', u'代码', u'单位', u'入库单号', u'产品类别', u'仓别', u'数量', u'单价', u'剩余数量',
+                    u'合计成本', u'合计销售价', u'退料数量', u'退料成本合计', u'审核时间', u'审核人', u'存放库位', u'备注']
+        return [u'出库单号', u'出库类别', u'产品', u'代码', u'单位', u'入库单号', u'产品类别', u'仓别', u'数量', u'剩余数量',
+                    u'合计销售价', u'退料数量', u'审核时间', u'审核人', u'存放库位', u'备注']
+
+
+
+class GoodsDeliverReturnQueryResource(resources.Resource):
+    def __init__(self, is_show_cost=True):
+        super(GoodsDeliverReturnQueryResource, self).__init__()
+        self.is_show_cost = is_show_cost
+        self.fields['no'] = Field(attribute='no')
+        self.fields['return_no'] = Field(attribute='return_no')
+        self.fields['type'] = Field(attribute='type')
+        self.fields['name'] = Field(attribute='name')
+        self.fields['model'] = Field(attribute='model')
+        self.fields['product_type'] = Field(attribute='product_type')
+        self.fields['warehouse'] = Field(attribute='warehouse')
+        self.fields['cur_count'] = Field(attribute='cur_count')
+        self.fields['return_count'] = Field(attribute='return_count')
+        self.fields['create_user'] = Field(attribute='create_user')
+        self.fields['create_time'] = Field(attribute='create_time')
+        self.fields['warehouse_place'] = Field(attribute='warehouse_place')
+        self.fields['reason'] = Field(attribute='reason')
+        self.fields['notes'] = Field(attribute='notes')
+        if is_show_cost:
+            self.fields['return_cost'] = Field(attribute='return_cost')
+            fields = ('no', 'return_no', 'type', 'name', 'model', 'product_type', 'warehouse',
+                      'cur_count', 'return_count', 'return_cost', 'create_user', 'create_time'
+                      , 'warehouse_place', 'reason', 'notes')
+        else:
+            fields = ('no', 'return_no', 'type', 'name', 'model', 'product_type', 'warehouse',
+                      'cur_count', 'return_count', 'create_user', 'create_time'
+                      , 'warehouse_place', 'reason', 'notes')
+        self._meta.export_order = fields
+
+    def get_export_headers(self):
+        if self.is_show_cost:
+            return [u'出库单号', u'退库单号', u'出库类别', u'产品', u'代码', u'产品类别', u'仓别', u'剩余数量'
+                , u'退库数量', u'退库成本合计', u'创建人',u'创建时间', u'存放库位', u'退货原因', u'产品备注']
+        return [u'出库单号', u'退库单号', u'出库类别', u'产品', u'代码', u'产品类别', u'仓别', u'剩余数量'
+            , u'退库数量', u'创建人', u'创建时间', u'存放库位', u'退货原因', u'产品备注']

+ 328 - 0
apps/order/serializers.py

@@ -0,0 +1,328 @@
+#coding=utf-8
+
+from rest_framework import serializers
+
+from apps.base import Formater
+from apps.exceptions import CustomError
+from apps.foundation.models import BizLog
+from apps.goods.models import GoodsGodownEntryDetail
+from apps.purchase.models import GodownEntryDetail
+from apps.serializer_errors import dump_serializer_errors
+from apps.warehouse.biz import BizWarehouse
+from apps.warehouse.models import WarehouseStock, InventoryDetail
+from models import SaleOrder, SaleOrderDetail, GoodsDeliver, GoodsDeliverDetail, GoodsDeliverReturn, \
+    GoodsDeliverReturnDetail
+from libs.booleancharfield import BooleanCharField, PriceShowCharField
+from apps import base
+from libs.booleancharfield import CountShowCharField, PriceShowCharField, AmountShowCharField
+
+
+class SaleOrderSerializer(serializers.ModelSerializer):
+    create_time = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    check_time = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    check_status_text = serializers.CharField(source='get_status_display', read_only=True)
+    check_user_text = serializers.CharField(source='check_user.name', read_only=True)
+    create_user_text = serializers.CharField(source='create_user.name', read_only=True)
+    customer_name = serializers.CharField(source='customer.name', read_only=True)
+    customer_id = serializers.CharField(source='customer.id', read_only=True)
+    customer_tel = serializers.CharField(source='customer.mobile', read_only=True)
+    products = serializers.SerializerMethodField()
+    count = CountShowCharField(read_only=True)
+    amount = AmountShowCharField(read_only=True)
+
+    class Meta:
+        model = SaleOrder
+        fields = '__all__'
+
+    def get_products(self, obj):
+        data = []
+        rows = SaleOrderDetail.objects.filter(main=obj)
+        data.extend([s[0] for s in rows.values_list('goods__product_base__name')])
+        return ','.join(data)
+
+    @staticmethod
+    def factory(user, data,id=None):
+        if id:
+            instance = SaleOrder.getById(id)
+        else:
+            instance = None
+        serializer = SaleOrderSerializer(instance, data=data)
+        serializer.user = user
+        return serializer
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+    def create(self, validated_data):
+        validated_data['create_user'] = self.user
+        validated_data['department'] = self.user.department
+        instance = super(SaleOrderSerializer, self).create(validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加订单[%s],id=%d" % (instance.no, instance.id),
+            validated_data
+        )
+        return instance
+
+    def update(self, instance, validated_data):
+        instance = super(SaleOrderSerializer, self).update(instance, validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.UPDATE,
+            u"修改订单[%s],id=%d" % (instance.no, instance.id),
+            validated_data
+        )
+        return instance
+
+class SaleOrderDetailSerializer(serializers.ModelSerializer):
+    name = serializers.CharField(source='goods.product_base.name', read_only=True)
+    model = serializers.CharField(source='goods.product_base.model', read_only=True)
+    quality_request_text = serializers.CharField(source='quality_request.name', read_only=True)
+    quality_request_id = serializers.CharField(source='quality_request.id', read_only=True)
+    count = CountShowCharField()
+    price = PriceShowCharField()
+    amount = AmountShowCharField(read_only=True)
+
+    class Meta:
+        model = SaleOrderDetail
+        fields = '__all__'
+
+    @staticmethod
+    def factory(user, data):
+        instances = None
+        serializer = SaleOrderDetailSerializer(instances, data=data)
+        serializer.user = user
+        return serializer
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+    def validate(self, data):
+        data['price'] = Formater.formatPrice(data['price'])
+        data['count'] = Formater.formatCount(data['count'])
+        data['amount'] = data['count'] * data['price']
+        return data
+
+    def create(self, validated_data):
+        instance = super(SaleOrderDetailSerializer, self).create(validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加订单明细[%s],id=%d" % (instance.main.no, instance.id),
+            validated_data
+        )
+        return instance
+
+class GoodsDeliverSerializer(serializers.ModelSerializer):
+    customer_id = serializers.CharField(source='customer.id', read_only=True)
+    customer_name = serializers.CharField(source='customer.name', read_only=True)
+    customer_tel = serializers.CharField(source='customer.mobile', read_only=True)
+    agent_user_text = serializers.CharField(source='agent_user.name', read_only=True)
+    agent_department_text = serializers.CharField(source='agent_department.name', read_only=True)
+    sale_order_no = serializers.CharField(source='sale_order.no', read_only=True)
+    status = serializers.CharField(read_only=True)
+    status_text = serializers.CharField(source='get_status_display', read_only=True)
+    create_user_text = serializers.CharField(source='create_user.name', read_only=True)
+    create_time = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    check_user_text = serializers.CharField(source='check_user.name', read_only=True)
+    check_time = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    warehouse_text = serializers.CharField(source='warehouse.name', read_only=True)
+    total_count = CountShowCharField(read_only=True)
+    total_cost = AmountShowCharField(read_only=True)
+    total_amount = AmountShowCharField(read_only=True)
+
+    class Meta:
+        model = GoodsDeliver
+        fields = '__all__'
+
+    @staticmethod
+    def factory(user, data, id=None):
+        if id:
+            instance = GoodsDeliver.getById(id)
+        else:
+            instance = None
+        serializer =GoodsDeliverSerializer(instance, data=data)
+        serializer.user = user
+        return serializer
+
+    def create(self, validated_data):
+        validated_data['create_user'] = self.user
+        validated_data['department'] = self.user.department
+        deliver = GoodsDeliver.objects.create(**validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加成品出库记录[%s],id=%d" % (deliver.no, deliver.id),
+            validated_data
+        )
+        return deliver
+
+    def update(self, instance, validated_data):
+        instance = super(GoodsDeliverSerializer, self).update(instance, validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.UPDATE,
+            u"修改成品出库记录[%s],id=%d" % (instance.no, instance.id),
+            validated_data
+        )
+        return instance
+
+    def validate(self, data):
+        data['agent_department'] = data['agent_user'].department
+        return data
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+
+class GoodsDeliverDetailSerializer(serializers.ModelSerializer):
+    name = serializers.CharField(source='product_base.product_base.name', read_only=True)
+    model = serializers.CharField(source='product_base.product_base.model', read_only=True)
+    unit_text = serializers.CharField(source='product_base.product_base.unit', read_only=True)
+    count = CountShowCharField()
+    price = PriceShowCharField()
+    no = serializers.SerializerMethodField()
+    warehouse_stock_count = serializers.CharField(source='warehouse_stock.count', read_only=True)
+    total_cost = AmountShowCharField()
+    total_amount = AmountShowCharField(read_only=True)
+
+    class Meta:
+        model = GoodsDeliverDetail
+        fields = '__all__'
+
+    @staticmethod
+    def factory(user, data):
+        serializer = GoodsDeliverDetailSerializer(None, data=data)
+        serializer.user = user
+        return serializer
+
+    def get_no(self, obj):
+        instance = GoodsGodownEntryDetail.objects.filter(stock_record=obj.warehouse_stockrecord).first()
+        if instance:
+            no = instance.main.no
+        else:
+            no = InventoryDetail.objects.filter(warehouse_stock_record=obj.warehouse_stockrecord).first().main.no
+        return no
+
+    def create(self, validated_data):
+        instance = GoodsDeliverDetail.objects.create(**validated_data)
+
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加成品出库明细[%s],id=%d" % (instance.product_base.product_base.name, instance.id),
+            validated_data
+        )
+        return instance
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+    def validate(self, data):
+        data['count'] = Formater.formatCount(data['count'])
+        data['price'] = Formater.formatPrice(data['price'])
+        data['total_amount'] = data['count'] * data['price']
+        data['total_cost'] = Formater.formatAmount(data['total_cost'])
+        warehouse_stock = WarehouseStock.getByWarehouseAndProduct(data['main'].warehouse, data['product_base'].product_base)
+        data['warehouse_stock'] = warehouse_stock
+
+        return data
+
+
+class GoodsDeliverReturnViewSerializer(serializers.ModelSerializer):
+    create_user_text = serializers.CharField(source='create_user.name', read_only=True)
+    create_time = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    customer_name = serializers.CharField(source='customer.name', read_only=True)
+    customer_tel = serializers.CharField(source='customer.mobile', read_only=True)
+    warehouse_text = serializers.CharField(source='warehouse.name', read_only=True)
+    total_count = CountShowCharField(read_only=True)
+    total_cost = AmountShowCharField(read_only=True)
+    total_amount = AmountShowCharField(read_only=True)
+    return_count = CountShowCharField(read_only=True)
+    return_cost = AmountShowCharField(read_only=True)
+
+    class Meta:
+        model = GoodsDeliver
+        fields = '__all__'
+
+
+class GoodsDeliverReturnSerializer(serializers.ModelSerializer):
+    create_time = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    class Meta:
+        model = GoodsDeliverReturn
+        fields = '__all__'
+
+    @staticmethod
+    def factory(user, data):
+        serializer = GoodsDeliverReturnSerializer(data=data)
+        serializer.user = user
+        return serializer
+
+    def create(self, validated_data):
+        validated_data['create_user'] = self.user
+        validated_data['department'] = self.user.department
+        deliver_return = GoodsDeliverReturn.objects.create(**validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加成品退库记录[%s],id=%d" % (deliver_return.no, deliver_return.id),
+            validated_data
+        )
+        return deliver_return
+
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+class GoodsDeliverReturnDetailSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = GoodsDeliverReturnDetail
+        fields = '__all__'
+
+    @staticmethod
+    def factory(user, data):
+        serializer = GoodsDeliverReturnDetailSerializer(data=data)
+        serializer.user = user
+        return serializer
+
+    def create(self, validated_data):
+        deliver_return_detail = GoodsDeliverReturnDetail.objects.create(**validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加成品退库明细记录[%s],id=%d" % (deliver_return_detail.main.no, deliver_return_detail.id),
+            validated_data
+        )
+        return deliver_return_detail
+
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+    def validate(self, data):
+        data['return_count'] = Formater.formatCount(data['return_count'])
+        warehouse_stock = WarehouseStock.getByWarehouseAndProduct(data['deliver_detail'].main.warehouse, data['product_base'].product_base)
+        data['warehouse_stock'] = warehouse_stock
+        data['warehouse_record'] = BizWarehouse.deliveredBack(data['deliver_detail'].warehouse_record, data['return_count'])
+        data['return_cost'] = data['warehouse_record'].amount
+        return data

+ 37 - 0
apps/order/urls.py

@@ -0,0 +1,37 @@
+#coding=utf-8
+
+from django.conf.urls import url
+
+from views import *
+
+urlpatterns = (
+    url(r'^sale_order/data/$', sale_order_list),
+    url(r'^sale_order/export/$', sale_order_export),
+    url(r'^sale_order/save/$', sale_order_save),
+    url(r'^sale_order/detail/$', sale_order_detail),
+    url(r'^sale_order/check/$', sale_order_check),
+    url(r'^sale_order/delete/$', sale_order_delete),
+    url(r'^sale_order/export_detail/$', sale_order_export_detail),
+
+    url(r'^sale_order/select/$', sale_order_select),
+
+    url(r'^deliver/data/$', deliver_list),
+    url(r'^deliver/export/$', deliver_export),
+    url(r'^deliver/save/$', deliver_save),
+    url(r'^deliver/check/$', deliver_check),
+    url(r'^deliver/delete/$', deliver_delete),
+    url(r'^deliver/detail/$', deliver_detail),
+
+    url(r'^deliver_query/data/$', deliver_query_list),
+    url(r'^deliver_query/export/$', deliver_query_export),
+    url(r'^deliver_query/detail/$', deliver_query_detail),
+
+    url(r'^deliver_return/data/$', deliver_return_list),
+    url(r'^deliver_return/select_data/$', deliver_return_select_list),
+    url(r'^deliver_return/detail/$', deliver_return_detail),
+    url(r'^deliver_return/save/$', deliver_return_save),
+    
+    url(r'^deliver_return_query/data/$', deliver_return_query_list),
+    url(r'^deliver_return_query/export/$', deliver_return_query_export),
+    url(r'^deliver_return_query/detail/$', deliver_return_query_detail),
+)

+ 968 - 0
apps/order/views.py

@@ -0,0 +1,968 @@
+# coding=utf-8
+
+import traceback
+import json
+from django.views.decorators.csrf import csrf_exempt
+from django.db import transaction
+from apps.account.decorators import token_required, permission_required, valid_permission, isHasPermissions
+from apps.account.models import User
+from apps.order.models import SaleOrder, SaleOrderDetail, GoodsDeliver, GoodsDeliverDetail, GoodsDeliverReturn,GoodsDeliverReturnDetail
+from django.db.models import Q, F, Sum
+from django.utils import timezone
+
+from apps.goods.models import GoodsGodownEntryDetail
+from apps.warehouse.biz import BizWarehouse, GetWarehouseSrockRecord
+from apps.warehouse.models import Warehouse, WarehouseStock, WarehouseRecord, InventoryDetail
+from libs.http import JSONResponse, JSONError, DataGridJSONResponse
+from _mysql_exceptions import IntegrityError
+from django.db.models import ProtectedError
+from libs import utils
+from apps.exceptions import CustomError, ExportChange
+from apps.order.filters import SaleOrderFilter, GoodsDeliverFilter, GoodsDeliverReturnFilter
+from apps.order.serializers import SaleOrderSerializer, SaleOrderDetailSerializer, GoodsDeliverSerializer, \
+    GoodsDeliverDetailSerializer, GoodsDeliverReturnViewSerializer, GoodsDeliverReturnSerializer, \
+    GoodsDeliverReturnDetailSerializer
+from apps.goods.models import Goods
+from apps.product.models import ProductBase
+from apps.base import Formater
+from django.conf import settings
+from apps.foundation.models import BizLog, Option
+from resources import SaleOrderResource, SaleOrderDetailResource, GoodsDeliverDetailResource, GoodsDeliverResource, \
+    GoodsDeliverQueryResource, GoodsDeliverReturnQueryResource
+
+
+@csrf_exempt
+@permission_required('order.view_sale_order')
+def sale_order_list(request):
+    department_ids = request.user.getSubDepartmentIds()
+    user_ids = request.user.getSubEmployeeIds()
+    rows = SaleOrder.objects.filter(Q(department_id__in=department_ids) | Q(create_user_id__in=user_ids) | Q(create_user=request.user))
+    f = SaleOrderFilter(request.GET, queryset=rows)
+    total_row = f.qs.aggregate(sum_count=Sum('count'), sum_amount=Sum('amount'))
+    more = {
+        'sum_count': Formater.formatCountShow(total_row['sum_count']),
+        'sum_amount': Formater.formatAmountShow(total_row['sum_amount'])
+    }
+    rows, total = utils.get_page_data(request, f.qs)
+    serializer = SaleOrderSerializer(rows, many=True)
+    return DataGridJSONResponse(serializer.data, total, more)
+
+
+@csrf_exempt
+@permission_required('order.export_sale_order')
+def sale_order_export(request):
+    department_ids = request.user.getSubDepartmentIds()
+    user_ids = request.user.getSubEmployeeIds()
+    rows = SaleOrder.objects.filter(Q(department_id__in=department_ids) | Q(create_user_id__in=user_ids) | Q(create_user=request.user))
+    f = SaleOrderFilter(request.GET, queryset=rows)
+    serializer = SaleOrderSerializer(f.qs, many=True)
+    export_data = ExportChange.dict_to_obj(serializer)
+    export_data = SaleOrderResource().export(export_data)
+    filename = utils.attachment_save(export_data)
+    BizLog.objects.addnew(request.user, BizLog.EXPORT, u"导出销售单")
+    return JSONResponse({'filename': filename})
+
+@csrf_exempt
+@permission_required('order.add_sale_order')
+def sale_order_save(request):
+    source = request.GET.get('source')
+    id = request.GET.get('id')
+    # detail_id:添加保存,销售计划直接保存,不需要添加明细detail_id=-1,添加明细保存默认detail_id=0,修改保存,detail_id=明细id
+    detail_id = -1
+    if source == 'touch':
+        detail_id = json.loads(request.body)['detail']
+    data = json.loads(request.body)
+
+    try:
+        with transaction.atomic():
+            pb = SaleOrderSerializer.factory(request.user, data['order_data'],id)
+            if pb.instance and pb.instance.status == settings.PASS:
+                raise CustomError(u'该订单已审核, 不允许修改')
+            pb = pb.validSave()
+            if source == 'touch' and detail_id >= 0:
+                # 手机端保存,如果是修改,先删除要修改的明细
+                SaleOrderDetail.objects.filter(id=int(detail_id)).delete()
+                for item in data['items']:
+                    item['main'] = pb.id
+                    pbd = SaleOrderDetailSerializer.factory(request.user, item)
+                    pbd.validSave()
+            else:
+                SaleOrderDetail.objects.filter(main_id=pb.id).delete()
+                for item in data['items']:
+                    item['main'] = pb.id
+                    pbd = SaleOrderDetailSerializer.factory(request.user, item)
+                    pbd.validSave()
+
+            pb.updateAmount()
+
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'保存失败')
+    return JSONResponse(pb.id)
+
+@csrf_exempt
+@token_required
+def sale_order_detail(request):
+    id = request.GET.get('id')
+    warehouse_id = request.GET.get('warehouse_id')
+    sale_order = SaleOrder.getById(id)
+    rows = SaleOrderDetail.objects.filter(main_id=id)
+    company = SaleOrder.getById(id).department.getCompany()
+    if sale_order.status == settings.PASS:
+        check_status = u'已审核'
+    else:
+        check_status = u'待审核'
+    main_data = {
+        'id': sale_order.id,
+        'no': sale_order.no,
+        'total_count': Formater.formatCountShow(sale_order.count),
+        'total_amount': Formater.formatAmountShow(sale_order.amount),
+        'name': sale_order.customer.name,
+        'mobile': sale_order.customer.mobile,
+        'customer_id': sale_order.customer.id,
+        'status': check_status,
+        'status_id': sale_order.status,
+        'check_user': sale_order.check_user and sale_order.check_user.name or '',
+        'create_user': sale_order.create_user and sale_order.create_user.name or '',
+        'check_time': sale_order.check_time and Formater.formatStrTime(sale_order.check_time) or '',
+        'create_time': Formater.formatStrTime(sale_order.create_time),
+        'notes': sale_order.notes or '',
+    }
+    data = {
+        'main_data':main_data,
+        'company': company.name,
+        'items_data': []
+    }
+    for row in rows:
+        item = {
+            'id': row.id,
+            'goods_id': row.goods_id,
+            'product_id': row.goods.product_base_id,
+            'unit': row.goods.product_base.unit,
+            'name': row.goods.product_base.name,
+            'model': row.goods.product_base.model,
+            'quality_request': row.quality_request and row.quality_request.id or '',
+            'quality_request_text': row.quality_request and row.quality_request.name or '',
+            'warehouse_place': row.goods.product_base.warehouse_place,
+            'count': Formater.formatCountShow(row.count),
+            'price': Formater.formatPriceShow(row.price),
+            'amount': Formater.formatAmountShow(row.amount)
+        }
+        if warehouse_id:
+            record_data = GetWarehouseSrockRecord.getRecord(row.goods.product_base.id, warehouse_id)
+            item['record_data'] = record_data
+        data['items_data'].append(item)
+    return JSONResponse(data)
+
+@csrf_exempt
+@permission_required('order.delete_sale_order')
+def sale_order_delete(request):
+    id = int(request.GET.get('id'))
+    try:
+        with transaction.atomic():
+            order = SaleOrder.getById(id)
+            if order.status != settings.DEFAULT:
+                raise CustomError(u'该订单已审核, 不允许删除')
+            BizLog.objects.addnew(request.user, BizLog.DELETE, u"删除销售订单[%s],id=%d" % (order.no, order.id))
+            SaleOrderDetail.objects.filter(main=order).delete()
+            order.delete()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except ProtectedError:
+        return JSONError(u'该销售订单已被引用,禁止删除!')
+    except IntegrityError:
+        return JSONError(u'该销售订单已被引用,禁止删除!')
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'删除失败!')
+
+    return JSONResponse({})
+
+@csrf_exempt
+@permission_required('order.check_sale_order')
+def sale_order_check(request):
+    id = request.GET.get('id')
+    status = int(request.GET.get('status'))
+
+    try:
+        with transaction.atomic():
+            order = SaleOrder.getById(id)
+            if status == settings.PASS:
+                if order.status != settings.DEFAULT:
+                    raise CustomError(u'该订单已审核')
+                order.status = settings.PASS
+                order.check_user = request.user
+                order.check_time = timezone.now()
+                BizLog.objects.addnew(
+                    request.user,
+                    BizLog.CHECK,
+                    u"审核销售订单[%s],id=%d" % (order.no, order.id),
+                )
+            else:
+                if order.status == settings.DEFAULT:
+                    raise CustomError(u'该订单尚未审核')
+                deliver = GoodsDeliver.objects.filter(sale_order=order).first()
+                if deliver:
+                    raise CustomError(u'该订单已转出库, 不允许撤销审核')
+                order.status = settings.DEFAULT
+                order.check_user = None
+                order.check_time = None
+                BizLog.objects.addnew(
+                    request.user,
+                    BizLog.CHECK,
+                    u"取消审核销售订单[%s],id=%d" % (order.no, order.id),
+                )
+            order.save()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'审核失败')
+    return JSONResponse({})
+
+@csrf_exempt
+@permission_required('order.export_sale_order')
+def sale_order_export_detail(request):
+    id = request.GET.get('id')
+    serializer = SaleOrderDetailSerializer(SaleOrderDetail.objects.filter(main_id=id), many=True)
+    export_data = ExportChange.dict_to_obj(serializer)
+    export_data = SaleOrderDetailResource().export(export_data)
+    filename = utils.attachment_save(export_data)
+    BizLog.objects.addnew(request.user, BizLog.EXPORT, u"导出销售单明细")
+    return JSONResponse({'filename': filename})
+
+@token_required
+def sale_order_select(request):
+    param = request.GET.get('keywords')
+    rows = SaleOrder.objects.filter()
+    if param:
+        rows = rows.filter(no__icontains=param)
+        serializer = SaleOrderSerializer(rows, many=True)
+        return JSONResponse(serializer.data)
+
+
+@csrf_exempt
+@permission_required('order.view_goods_deliver')
+def deliver_list(request):
+    product_notes = request.GET.get('product_notes')
+    warehouses_ids = Warehouse.getManagerWarehouses(request.user)
+    department_ids = request.user.getSubDepartmentIds()
+    user_ids = request.user.getSubEmployeeIds()
+    rows = GoodsDeliver.objects.filter(warehouse_id__in=warehouses_ids)
+    rows = rows.filter(
+        Q(department_id__in=department_ids) | Q(create_user_id__in=user_ids) | Q(create_user=request.user))
+
+    if product_notes:
+        g_ids = rows.values_list('id')
+        d_ids = GoodsDeliverDetail.objects.filter(main_id__in=g_ids, notes__icontains=product_notes).values_list('main_id')
+        rows = rows.filter(id__in=d_ids)
+    f = GoodsDeliverFilter(request.GET, queryset=rows)
+    total_row = f.qs.aggregate(total_count=Sum('total_count'), total_cost=Sum('total_cost'),
+                               total_amount=Sum('total_amount'))
+    more = {
+        'total_count': Formater.formatCountShow(total_row['total_count']),
+        'total_cost': Formater.formatAmountShow(total_row['total_cost']),
+        'return_count': Formater.formatAmountShow(total_row['total_amount']),
+    }
+    rows, total = utils.get_page_data(request, f.qs)
+    serializer = GoodsDeliverSerializer(rows, many=True)
+    return DataGridJSONResponse(serializer.data, total, more)
+
+
+@csrf_exempt
+@permission_required('order.export_goods_deliver')
+def deliver_export(request):
+    id = request.GET.get('id')
+    try:
+        perm = 'goods.view_goods_cost'
+        is_show_cost = isHasPermissions(request.user, perm)
+        if id:
+            instance = GoodsDeliver.getById(id)
+            deliver_detail = GoodsDeliverDetail.objects.filter(main=instance)
+            serializer = GoodsDeliverDetailSerializer(deliver_detail, many=True)
+            export_data = ExportChange.dict_to_obj(serializer)
+            export_data = GoodsDeliverDetailResource(is_show_cost).export(export_data)
+            filename = utils.attachment_save(export_data)
+            BizLog.objects.addnew(request.user, BizLog.EXPORT, u"导出成品出库单[%s]明细,id=%d" % (instance.no, instance.id))
+        else:
+            warehouses_ids = Warehouse.getManagerWarehouses(request.user)
+            department_ids = request.user.getSubDepartmentIds()
+            user_ids = request.user.getSubEmployeeIds()
+            rows = GoodsDeliver.objects.filter(warehouse_id__in=warehouses_ids)
+            rows = rows.filter(
+                Q(department_id__in=department_ids) | Q(create_user_id__in=user_ids) | Q(create_user=request.user))
+            f = GoodsDeliverFilter(request.GET, queryset=rows)
+            serializer = GoodsDeliverSerializer(f.qs, many=True)
+            export_data = ExportChange.dict_to_obj(serializer)
+            export_data = GoodsDeliverResource(is_show_cost).export(export_data)
+            filename = utils.attachment_save(export_data)
+            BizLog.objects.addnew(request.user, BizLog.EXPORT, u"导出成品出库单")
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'导出失败!')
+    return JSONResponse({'filename': filename})
+
+
+@csrf_exempt
+@permission_required('order.add_goods_deliver')
+def deliver_save(request):
+    source = request.GET.get('source')
+    id = request.GET.get('id')
+    detail_id = -1
+    if source == 'touch':
+        main_data = json.loads(request.body)['main']
+        items_data = json.loads(request.body)['item']
+        detail_id = json.loads(request.body)['detail']
+    else:
+        main_data = json.loads(request.POST.get('main'))
+        items_data = json.loads(request.POST.get('item'))
+    try:
+        with transaction.atomic():
+            serializer = GoodsDeliverSerializer.factory(request.user, main_data, id)
+            if serializer.instance and serializer.instance.status == settings.PASS:
+                raise CustomError(u'该出库单已审核,禁止修改!')
+            serializer = serializer.validSave()
+            if source == 'touch' and detail_id >= 0:
+                # 手机端保存,如果是修改,先删除要修改的明细
+                GoodsDeliverDetail.objects.filter(id=int(detail_id)).delete()
+                for item in items_data:
+                    product_base_id = Goods.getById(item['product_base']).product_base.id
+                    warehouse_stock = WarehouseStock.getByWarehouseAndProduct(serializer.warehouse,product_base_id)
+                    instance = GoodsDeliverDetail.objects.create(
+                        main_id=id,
+                        product_base_id=item['product_base'],
+                        warehouse_stock=warehouse_stock,
+                        warehouse_stockrecord_id=item['warehouse_stockrecord'],
+                        total_cost=Formater.formatAmount(item['total_cost']),
+                        count=Formater.formatCount(item['count']),
+                        price=Formater.formatPrice(item['price']),
+                        total_amount=Formater.formatPrice(item['price']) * Formater.formatCount(item['count']),
+                        notes=item['notes'],
+                    )
+                    BizLog.objects.addnew(
+                        request.user,
+                        BizLog.INSERT,
+                        u"APP添加原料/耗材出库明细[%s],id=%d" % (instance.product_base.product_base.name, instance.id),
+                        item
+                    )
+            else:
+                GoodsDeliverDetail.objects.filter(main=serializer).delete()
+                for item in items_data:
+                    item['main'] = serializer.id
+                    item['product_base'] = Goods.getByBaseId(item['product_base']).id
+                    detail_serializer = GoodsDeliverDetailSerializer.factory(request.user, data=item)
+                    detail_serializer.validSave()
+            serializer.update_total()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'保存失败!')
+    return JSONResponse(serializer.id)
+
+
+@csrf_exempt
+@permission_required('order.delete_goods_deliver')
+def deliver_delete(request):
+    id = request.GET.get('id')
+    try:
+        with transaction.atomic():
+            instance = GoodsDeliver.getById(id)
+            if instance.status == settings.PASS:
+                raise CustomError(u'该出库单已审核,禁止删除!')
+
+            BizLog.objects.addnew(request.user, BizLog.DELETE, u"删除成品出库单[%s],id=%d" % (instance.no, instance.id))
+            GoodsDeliverDetail.objects.filter(main=instance).delete()
+            instance.delete()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except ProtectedError:
+        return JSONError(u'该出库单已被以用,禁止删除!')
+    except IntegrityError:
+        return JSONError(u'该出库单已被以用,禁止删除!')
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'删除失败!')
+
+    return JSONResponse({})
+
+
+@token_required
+def deliver_detail(request):
+    id = request.GET.get('id')
+    instance = GoodsDeliver.getById(id)
+    company = instance.department.getCompany()
+    if instance.status == settings.PASS:
+        status_text = u'已审核'
+    else:
+        status_text = u'待审核'
+    main_data = {
+        'sale_order_id': instance.sale_order_id or '',
+        'id': instance.id,
+        'company': company.name,
+        'sale_order_no': instance.sale_order and instance.sale_order.no or '',
+        'customer_name': instance.customer and instance.customer.name or '',
+        'customer_id': instance.customer_id or '',
+        'customer_tel': instance.customer and instance.customer.mobile or '',
+        'agent_user_id': instance.agent_user_id,
+        'agent_user_name': instance.agent_user.name,
+        'agent_department_name': instance.agent_department and instance.agent_department.name or '',
+        'warehouse_id': instance.warehouse_id,
+        'warehouse_name': instance.warehouse.name,
+        'create_user_name': instance.create_user.name,
+        'create_time': Formater.formatStrTime(instance.create_time),
+        'total_count': Formater.formatCountShow(instance.total_count),
+        'total_amount': Formater.formatAmountShow(instance.total_amount),
+        'total_cost': Formater.formatAmountShow(instance.total_cost),
+        'status': instance.status,
+        'status_text': status_text,
+        'check_user_text': instance.check_user and instance.check_user.name or ' ',
+        'check_time': Formater.formatStrTime(instance.create_time),
+        'notes': instance.notes,
+        'no': instance.no
+    }
+
+    data = {
+        'main_data': main_data,
+        'items_data': []
+    }
+    detail_rows = GoodsDeliverDetail.objects.filter(main=instance)
+    for detail_row in detail_rows:
+        no = ''
+        if detail_row.warehouse_stockrecord_id:
+            godownentry = GoodsGodownEntryDetail.objects.filter(stock_record=detail_row.warehouse_stockrecord).first()
+            if godownentry:
+                no = godownentry.main.no
+            else:
+                no = InventoryDetail.objects.filter(warehouse_stock_record=detail_row.warehouse_stockrecord).first().main.no
+        record_data = GetWarehouseSrockRecord.getRecord(detail_row.product_base.product_base.id, instance.warehouse_id)
+        item_data = {
+            'id': detail_row.product_base_id,
+            'product_base': detail_row.product_base.product_base_id,
+            'detail_id': detail_row.id,
+            'name': detail_row.product_base.product_base.name,
+            'model': detail_row.product_base.product_base.model,
+            'total_cost': Formater.formatAmountShow(detail_row.total_cost),
+            'total_amount': Formater.formatAmountShow(detail_row.total_amount),
+            'count': Formater.formatCountShow(detail_row.count),
+            'price': Formater.formatPriceShow(detail_row.price),
+            'warehouse_stock_count': Formater.formatCountShow(detail_row.warehouse_stock.count),
+            'unit': detail_row.product_base.product_base.unit or '',
+            'notes': detail_row.notes or '',
+            'entry_no': detail_row.warehouse_stockrecord_id,
+            'record_data': record_data,
+            'no': no,
+        }
+        data['items_data'].append(item_data)
+
+    return JSONResponse(data)
+
+
+
+
+@csrf_exempt
+@permission_required('order.check_goods_deliver')
+def deliver_check(request):
+    id = request.GET.get('id')
+    try:
+        with transaction.atomic():
+            instance = GoodsDeliver.getById(id)
+            if instance.status == settings.PASS:
+                raise CustomError(u'该出库单已审核')
+            deliver_details = GoodsDeliverDetail.objects.filter(main=instance)
+            for deliver_detail in deliver_details:
+                if instance.sale_order:
+                    type = WarehouseRecord.CK_XS
+                else:
+                    type = WarehouseRecord.CK_ZJ
+                warehouse_record = BizWarehouse.deliveredByStockRecord(type,
+                                                  deliver_detail.warehouse_stockrecord,
+                                                  deliver_detail.count)
+                deliver_detail.warehouse_record = warehouse_record
+                deliver_detail.total_cost = -warehouse_record.amount
+                deliver_detail.save()
+            instance.update_total()
+            instance.status = settings.PASS
+            instance.check_user = request.user
+            instance.check_time = timezone.now()
+            BizLog.objects.addnew(
+                request.user,
+                BizLog.CHECK,
+                u"审核成品出库[%s],id=%d" % (instance.no, instance.id),
+            )
+
+            instance.save()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'审核失败')
+    return JSONResponse({})
+
+
+@csrf_exempt
+@permission_required('order.view_goods_deliver_query')
+def deliver_query_list(request):# 成品出库查询
+    rows = get_filter_data(request)
+    total_row = rows.aggregate(total_count1=Sum('goods_deliver_detail_ref_warehouse_record__count'),
+                               total_count2=Sum('inventory_details_ref_warehouse_record__count'),
+                               total_cost1=Sum('goods_deliver_detail_ref_warehouse_record__total_cost'),
+                               total_cost2=Sum('inventory_details_ref_warehouse_record__amount'),
+                               total_amount=Sum('goods_deliver_detail_ref_warehouse_record__total_amount'),
+                               return_count=Sum('goods_deliver_detail_ref_warehouse_record__return_count'),
+                               return_cost=Sum('goods_deliver_detail_ref_warehouse_record__return_cost'))
+    more = {
+        'total_count': Formater.formatCountShow((total_row['total_count1'] or 0) + (total_row['total_count2'] or 0)),
+        'total_cost': Formater.formatAmountShow((total_row['total_cost1'] or 0) + (total_row['total_cost2'] or 0)),
+        'total_amount': Formater.formatAmountShow(total_row['total_amount']),
+        'return_count': Formater.formatCountShow(total_row['return_count']),
+        'return_cost': Formater.formatAmountShow(total_row['return_cost']),
+    }
+    rows, total = utils.get_page_data(request, rows)
+    data = get_deliver_query_data(rows)
+    return DataGridJSONResponse(data, total, more)
+
+@csrf_exempt
+@permission_required('order.export_goods_deliver_query')
+def deliver_query_export(request):
+    try:
+        rows = get_filter_data(request)
+        data = get_deliver_query_data(rows)
+        export_data = ExportChange.dict_to_obj2(data)
+        perm = 'goods.view_goods_cost'
+        is_show_cost = isHasPermissions(request.user, perm)
+        export_data = GoodsDeliverQueryResource(is_show_cost).export(export_data)
+        filename = utils.attachment_save(export_data)
+        BizLog.objects.addnew(request.user, BizLog.EXPORT, u"导出成品出库查询")
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'导出出库查询失败!')
+    return JSONResponse({'filename': filename})
+
+
+@token_required
+def deliver_query_detail(request):
+    rows = get_filter_data(request)
+    data = get_deliver_query_data(rows)
+    return JSONResponse(data)
+
+def get_filter_data(request):
+    happen_time = request.GET.get('happen_time')
+    no = request.GET.get('no')
+    create_user = request.GET.get('create_user')
+    name = request.GET.get('name')
+    model = request.GET.get('model')
+    type = request.GET.get('type')
+    warehouse = request.GET.get('warehouse')
+    notes = request.GET.get('notes')
+    rows = WarehouseRecord.objects.filter(product__type=2, type__in=[3,4,5]).order_by('-id')
+    if happen_time:
+        happen_time_begin = happen_time.split(' - ')[0]
+        happen_time_end = happen_time.split(' - ')[1] + ' 23:59:59'
+        rows = rows.filter(happen_time__gt=happen_time_begin,happen_time__lt=happen_time_end)
+    if no:
+        rows = rows.filter(Q(goods_deliver_detail_ref_warehouse_record__main__no__icontains=no) | Q(inventory_details_ref_warehouse_record__main__no__icontains=no))
+    if create_user:
+        rows = rows.filter(Q(goods_deliver_detail_ref_warehouse_record__main__create_user__name__icontains=create_user) |
+                           Q(inventory_details_ref_warehouse_record__main__create_user__name__icontains=create_user))
+    if notes:
+        rows = rows.filter(Q(goods_deliver_detail_ref_warehouse_record__notes__icontains=notes) |
+                           Q(inventory_details_ref_warehouse_record__notes__icontains=notes))
+    if name:
+        rows = rows.filter(product__name__icontains=name)
+    if model:
+        rows = rows.filter(product__model__icontains=model)
+    if type:
+        rows = rows.filter(type=type)
+    if warehouse:
+        rows = rows.filter(warehouse__name__icontains=warehouse)
+
+    warehouses_ids = Warehouse.getManagerWarehouses(request.user)
+    department_ids = request.user.getSubDepartmentIds()
+    user_ids = request.user.getSubEmployeeIds()
+    rows = rows.filter(warehouse_id__in=warehouses_ids)
+    rows = rows.filter(
+        Q(goods_deliver_detail_ref_warehouse_record__main__department_id__in=department_ids) |
+        Q(goods_deliver_detail_ref_warehouse_record__main__create_user_id__in=user_ids) |
+        Q(goods_deliver_detail_ref_warehouse_record__main__create_user=request.user) |
+        Q(inventory_details_ref_warehouse_record__main__department_id__in=department_ids) |
+        Q(inventory_details_ref_warehouse_record__main__create_user_id__in=user_ids) |
+        Q(inventory_details_ref_warehouse_record__main__create_user=request.user)
+    )
+    return rows
+
+def get_deliver_query_data(rows):
+    rows = rows.values(
+        'id',
+        'type',
+        'product__name',
+        'product__model',
+        'product__unit',
+        'product__type',
+        'product__warehouse_place',
+        # 盘亏
+        'inventory_details_ref_warehouse_record__count',
+        'inventory_details_ref_warehouse_record__price',
+        'inventory_details_ref_warehouse_record__amount',
+        'inventory_details_ref_warehouse_record__notes',
+
+
+        'warehouse__name',
+        'happen_time',
+        'cur_count',
+        'goods_deliver_detail_ref_warehouse_record__count',
+        'goods_deliver_detail_ref_warehouse_record__price',
+        'goods_deliver_detail_ref_warehouse_record__total_cost',
+        'goods_deliver_detail_ref_warehouse_record__total_amount',
+        'goods_deliver_detail_ref_warehouse_record__return_count',
+        'goods_deliver_detail_ref_warehouse_record__return_cost',
+        'goods_deliver_detail_ref_warehouse_record__notes',
+        'goods_deliver_detail_ref_warehouse_record__main__check_user__name',
+        'goods_deliver_detail_ref_warehouse_record__main__create_user__name',
+        'goods_deliver_detail_ref_warehouse_record__main__check_time',
+        'goods_deliver_detail_ref_warehouse_record__main__no',
+        'goods_deliver_detail_ref_warehouse_record__warehouse_stockrecord__goods_godown_entry_detail_ref_stock_record__main__no',
+        'goods_deliver_detail_ref_warehouse_record__warehouse_stockrecord__inventory_details_ref_warehouse_stock_record__main__no',
+        # 盘亏
+        'inventory_details_ref_warehouse_record__main__no',
+        'inventory_details_ref_warehouse_record__main__check_user__name',
+        'inventory_details_ref_warehouse_record__main__create_user__name',
+        'inventory_details_ref_warehouse_record__main__check_time',
+
+
+    )
+    data = []
+    for row in rows:
+        warehouse_record_type = WarehouseRecord.TYPE_CHOICES[row['type']][1]
+        product_type_text = ProductBase.TYPE_CHOICES[row['product__type']][1]
+
+        no = row['goods_deliver_detail_ref_warehouse_record__main__no']
+        check_user = row['goods_deliver_detail_ref_warehouse_record__main__check_user__name']
+        create_user = row['goods_deliver_detail_ref_warehouse_record__main__create_user__name']
+        check_time = Formater.formatStrTime(row['goods_deliver_detail_ref_warehouse_record__main__check_time'])
+        notes = row['goods_deliver_detail_ref_warehouse_record__notes']
+        name = row['product__name']
+        model = row['product__model']
+        unit = row['product__unit']
+        warehouse_place = row['product__warehouse_place']
+        count = Formater.formatCountShow(row['goods_deliver_detail_ref_warehouse_record__count'])
+        entry_no = row['goods_deliver_detail_ref_warehouse_record__warehouse_stockrecord__goods_godown_entry_detail_ref_stock_record__main__no']
+        if not entry_no:
+            entry_no = row['goods_deliver_detail_ref_warehouse_record__warehouse_stockrecord__inventory_details_ref_warehouse_stock_record__main__no']
+        price = Formater.formatPriceShow(row['goods_deliver_detail_ref_warehouse_record__price'])
+        total_cost = Formater.formatAmountShow(row['goods_deliver_detail_ref_warehouse_record__total_cost'])
+        total_amount = Formater.formatAmountShow(row['goods_deliver_detail_ref_warehouse_record__total_amount'])
+        return_count = Formater.formatCountShow(row['goods_deliver_detail_ref_warehouse_record__return_count'])
+        return_cost = Formater.formatAmountShow(row['goods_deliver_detail_ref_warehouse_record__return_cost'])
+
+        if row['type'] == WarehouseRecord.CK_PK:
+            no = row['inventory_details_ref_warehouse_record__main__no']
+            entry_no = ''
+            check_user = row['inventory_details_ref_warehouse_record__main__check_user__name']
+            create_user = row['inventory_details_ref_warehouse_record__main__create_user__name']
+            check_time = Formater.formatStrTime(row['inventory_details_ref_warehouse_record__main__check_time'])
+            notes = row['inventory_details_ref_warehouse_record__notes']
+            count = Formater.formatCountShow(row['inventory_details_ref_warehouse_record__count'])
+            price = Formater.formatPriceShow(row['inventory_details_ref_warehouse_record__price'])
+            total_cost = Formater.formatAmountShow(row['inventory_details_ref_warehouse_record__amount'])
+            total_amount = '0.0000'
+            return_count = '0.00'
+            return_cost = '0.0000'
+
+        item = {
+            'id': row['id'],
+            'type': warehouse_record_type,
+            'product_type': product_type_text,
+
+            'name': name,
+            'model': model,
+            'unit': unit,
+            'warehouse_place': warehouse_place,
+
+            'warehouse': row['warehouse__name'],
+            'happen_time': Formater.formatStrTime(row['happen_time']),
+            'cur_count': Formater.formatCountShow(row['cur_count']),
+
+            'count': count,
+            'price': price,
+            'total_cost': total_cost,
+            'total_amount': total_amount,
+            'return_count': return_count,
+            'return_cost': return_cost,
+
+            'check_user': check_user,
+            'create_user': create_user,
+            'check_time': check_time,
+            'notes': notes,
+            'no': no,
+            'entry_no': entry_no,
+        }
+        data.append(item)
+    return data
+
+
+@csrf_exempt
+@permission_required('order.view_goods_deliver_return')
+def deliver_return_list(request):
+    product_notes = request.GET.get('product_notes')
+    warehouses_ids = Warehouse.getManagerWarehouses(request.user)
+    department_ids = request.user.getSubDepartmentIds()
+    user_ids = request.user.getSubEmployeeIds()
+    rows = GoodsDeliver.objects.filter(status=settings.PASS, warehouse_id__in=warehouses_ids)
+    rows = rows.filter(
+        Q(department_id__in=department_ids) | Q(create_user_id__in=user_ids) | Q(create_user=request.user))
+
+    if product_notes:
+        g_ids = rows.values_list('id')
+        d_ids = GoodsDeliverDetail.objects.filter(main_id__in=g_ids, notes__icontains=product_notes).values_list('main_id')
+        rows = rows.filter(id__in=d_ids)
+    f = GoodsDeliverReturnFilter(request.GET, queryset=rows)
+    total_row = f.qs.aggregate(total_count=Sum('total_count'), total_cost=Sum('total_cost'),
+                               total_amount=Sum('total_amount'), return_count=Sum('return_count'),
+                               return_cost=Sum('return_cost'))
+    more = {
+        'total_count': Formater.formatCountShow(total_row['total_count']),
+        'total_cost': Formater.formatAmountShow(total_row['total_cost']),
+        'total_amount': Formater.formatAmountShow(total_row['total_amount']),
+        'return_count': Formater.formatCountShow(total_row['return_count']),
+        'return_cost': Formater.formatAmountShow(total_row['return_cost']),
+    }
+    rows, total = utils.get_page_data(request, f.qs)
+    serializer = GoodsDeliverReturnViewSerializer(rows, many=True)
+    return DataGridJSONResponse(serializer.data, total, more)
+
+@token_required
+def deliver_return_select_list(request):
+    id = int(request.GET.get('id'))
+
+    ids = GoodsDeliverReturnDetail.objects.filter(deliver_detail__main_id=id).values_list('main_id', flat=True)
+    rows = GoodsDeliverReturn.objects.filter(id__in=ids)
+    data = []
+    for row in rows:
+        data.append(row)
+    serializer = GoodsDeliverReturnSerializer(data, many=True)
+    return DataGridJSONResponse(serializer.data, rows.count())
+
+@token_required
+def deliver_return_detail(request):
+    id = request.GET.get('id')
+    ids = request.GET.get('ids')
+    data = {
+        'main_data': '',
+        'deliver_data': [],
+        'items_data': []
+    }
+    if ids:
+        id_list = ids.split(',')
+        deliver_returns = GoodsDeliverReturn.getByIds(id_list)
+        for deliver_return in deliver_returns:
+            deliver_data = {
+                'no': deliver_return.no,
+                'return_count': Formater.formatCountShow(deliver_return.return_count),
+                'return_cost': Formater.formatAmountShow(deliver_return.return_cost),
+                'reason': deliver_return.reason
+            }
+            data['deliver_data'].append(deliver_data)
+    instance = GoodsDeliver.getById(id)
+    company = instance.department.getCompany()
+    main_data = {
+        'customer_name': instance.customer and instance.customer.name or '',
+        'customer_tel': instance.customer and instance.customer.mobile or '',
+        'total_amount': Formater.formatAmountShow(instance.total_amount),
+        'warehouse_id': instance.warehouse_id,
+        'warehouse_name': instance.warehouse.name,
+        'create_user_name': instance.create_user.name,
+        'create_time': Formater.formatStrTime(instance.create_time),
+        'total_count': Formater.formatCountShow(instance.total_count),
+        'total_cost': Formater.formatAmountShow(instance.total_cost),
+        'return_count': Formater.formatCountShow(instance.return_count),
+        'return_cost': Formater.formatAmountShow(instance.return_cost),
+        'notes': instance.notes,
+        'no': instance.no,
+        'company': company.name,
+    }
+    data['main_data'] = main_data
+    detail_rows = GoodsDeliverDetail.objects.filter(main=instance)
+    for detail_row in detail_rows:
+        item_data = {
+            'id': detail_row.id,
+            'product_id': detail_row.product_base.id,
+            'name': detail_row.product_base.product_base.name,
+            'model': detail_row.product_base.product_base.model,
+            'total_cost': Formater.formatAmountShow(detail_row.total_cost),
+            'total_amount': Formater.formatAmountShow(detail_row.total_amount),
+            'count': Formater.formatCountShow(detail_row.count),
+            'price': Formater.formatPriceShow(detail_row.price),
+            'warehouse_stock_count': Formater.formatCountShow(detail_row.warehouse_stock.count),
+            'unit': detail_row.product_base.product_base.unit or '',
+            'cur_count': Formater.formatCountShow(detail_row.count - detail_row.return_count),
+            'notes': detail_row.notes or ''
+        }
+        data['items_data'].append(item_data)
+
+    return JSONResponse(data)
+
+@csrf_exempt
+@permission_required('order.add_goods_deliver_return')
+def deliver_return_save(request):
+    id = request.GET.get('id')
+    main_data = json.loads(request.POST.get('main'))
+    items_data = json.loads(request.POST.get('item'))
+    try:
+        with transaction.atomic():
+            serializer = GoodsDeliverReturnSerializer.factory(request.user, main_data)
+            serializer = serializer.validSave()
+            for item in items_data:
+                item['main'] = serializer.id
+                detail_serializer = GoodsDeliverReturnDetailSerializer.factory(request.user, data=item)
+                detail_serializer = detail_serializer.validSave()
+                detail_serializer.deliver_detail.updateReturn()
+            serializer.update_total()
+            deliver = GoodsDeliver.getById(id)
+            deliver.update_return()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'保存失败!')
+    return JSONResponse()
+
+
+
+@csrf_exempt
+@permission_required('order.view_goods_deliver_return_query')
+def deliver_return_query_list(request):# 成品退库查询
+    rows = get_return_filter_data(request)
+    total_row = rows.aggregate(return_count=Sum('goods_return_deliver_detail_ref_warehouse_record__return_count'),
+                               return_cost=Sum('goods_return_deliver_detail_ref_warehouse_record__return_cost'))
+    more = {
+        'return_count': Formater.formatCountShow(total_row['return_count']),
+        'return_cost': Formater.formatAmountShow(total_row['return_cost']),
+    }
+    rows, total = utils.get_page_data(request, rows)
+    data = get_deliver_return_query_data(rows)
+    return DataGridJSONResponse(data, total, more)
+
+@csrf_exempt
+@permission_required('order.export_goods_deliver_return_query')
+def deliver_return_query_export(request):
+    try:
+        rows = get_return_filter_data(request)
+        data = get_deliver_return_query_data(rows)
+        export_data = ExportChange.dict_to_obj2(data)
+        perm = 'goods.view_goods_cost'
+        is_show_cost = isHasPermissions(request.user, perm)
+        export_data = GoodsDeliverReturnQueryResource(is_show_cost).export(export_data)
+        filename = utils.attachment_save(export_data)
+        BizLog.objects.addnew(request.user, BizLog.EXPORT, u"导出成品退库查询")
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'导出成品退库查询失败!')
+    return JSONResponse({'filename': filename})
+
+
+@token_required
+def deliver_return_query_detail(request):
+    rows = get_return_filter_data(request)
+    data = get_deliver_return_query_data(rows)
+    return JSONResponse(data)
+
+def get_return_filter_data(request):
+    create_time = request.GET.get('create_time')
+    warehouse = request.GET.get('warehouse')
+    return_no = request.GET.get('return_no')
+    no = request.GET.get('no')
+    name = request.GET.get('name')
+    model = request.GET.get('model')
+    reason = request.GET.get('reason')
+    notes = request.GET.get('notes')
+    create_user = request.GET.get('create_user')
+    rows = WarehouseRecord.objects.filter(product__type=2, type=7).order_by('-id')
+    if create_time:
+        create_time_begin = create_time.split(' - ')[0]
+        create_time_end = create_time.split(' - ')[1] + ' 23:59:59'
+        rows = rows.filter(goods_return_deliver_detail_ref_warehouse_record__main__create_time__gt=create_time_begin,
+                           goods_return_deliver_detail_ref_warehouse_record__main__create_time__lt=create_time_end)
+    if no:
+        rows = rows.filter(goods_return_deliver_detail_ref_warehouse_record__deliver_detail__main__no__icontains=no)
+    if create_user:
+        rows = rows.filter(goods_return_deliver_detail_ref_warehouse_record__main__create_user__name__icontains=create_user)
+    if warehouse:
+        rows = rows.filter(goods_return_deliver_detail_ref_warehouse_record__deliver_detail__main__warehouse__name__icontains=warehouse)
+    if return_no:
+        rows = rows.filter(
+            goods_return_deliver_detail_ref_warehouse_record__main__no__icontains=return_no)
+    if notes:
+        rows = rows.filter(
+            goods_return_deliver_detail_ref_warehouse_record__notes__icontains=notes)
+    if reason:
+        rows = rows.filter(goods_return_deliver_detail_ref_warehouse_record__main__reason__icontains=reason)
+    if name:
+        rows = rows.filter(goods_return_deliver_detail_ref_warehouse_record__product_base__name__icontains=name)
+    if model:
+        rows = rows.filter(goods_return_deliver_detail_ref_warehouse_record__product_base__name__icontains=model)
+    warehouses_ids = Warehouse.getManagerWarehouses(request.user)
+    department_ids = request.user.getSubDepartmentIds()
+    user_ids = request.user.getSubEmployeeIds()
+    rows = rows.filter(warehouse_id__in=warehouses_ids)
+    rows = rows.filter(
+        Q(goods_return_deliver_detail_ref_warehouse_record__main__department_id__in=department_ids)
+        | Q(goods_return_deliver_detail_ref_warehouse_record__main__create_user_id__in=user_ids)
+        | Q(goods_return_deliver_detail_ref_warehouse_record__main__create_user=request.user))
+    return rows
+
+def get_deliver_return_query_data(rows):
+    rows = rows.values(
+        'id',
+        'type',
+        'goods_return_deliver_detail_ref_warehouse_record__product_base__product_base__name',
+        'goods_return_deliver_detail_ref_warehouse_record__product_base__product_base__model',
+        'goods_return_deliver_detail_ref_warehouse_record__product_base__product_base__type',
+        'goods_return_deliver_detail_ref_warehouse_record__product_base__product_base__warehouse_place',
+        'warehouse__name',
+        'cur_count',
+        'goods_return_deliver_detail_ref_warehouse_record__return_count',
+        'goods_return_deliver_detail_ref_warehouse_record__return_cost',
+        'goods_return_deliver_detail_ref_warehouse_record__notes',
+        'goods_return_deliver_detail_ref_warehouse_record__main__create_user__name',
+        'goods_return_deliver_detail_ref_warehouse_record__main__create_time',
+        'goods_return_deliver_detail_ref_warehouse_record__main__reason',
+        'goods_return_deliver_detail_ref_warehouse_record__main__no',
+        'goods_return_deliver_detail_ref_warehouse_record__deliver_detail__main__no',
+    )
+    data = []
+    for row in rows:
+        warehouse_record_type = WarehouseRecord.TYPE_CHOICES[row['type']][1]
+        product_type_text = ProductBase.TYPE_CHOICES[row['goods_return_deliver_detail_ref_warehouse_record__product_base__product_base__type']][1]
+        item = {
+            'id': row['id'],
+            'type': warehouse_record_type,
+            'product_type': product_type_text,
+
+            'name': row['goods_return_deliver_detail_ref_warehouse_record__product_base__product_base__name'],
+            'model': row['goods_return_deliver_detail_ref_warehouse_record__product_base__product_base__model'],
+            'warehouse_place': row['goods_return_deliver_detail_ref_warehouse_record__product_base__product_base__warehouse_place'],
+
+            'warehouse': row['warehouse__name'],
+            'cur_count': Formater.formatCountShow(row['cur_count']),
+
+            'create_user': row['goods_return_deliver_detail_ref_warehouse_record__main__create_user__name'],
+            'notes': row['goods_return_deliver_detail_ref_warehouse_record__notes'],
+            'create_time': Formater.formatStrTime(row['goods_return_deliver_detail_ref_warehouse_record__main__create_time']),
+            'return_count': Formater.formatCountShow(row['goods_return_deliver_detail_ref_warehouse_record__return_count']),
+            'return_cost': Formater.formatAmountShow(row['goods_return_deliver_detail_ref_warehouse_record__return_cost']),
+            'reason': row['goods_return_deliver_detail_ref_warehouse_record__main__reason'],
+            'return_no': row['goods_return_deliver_detail_ref_warehouse_record__main__no'],
+
+            'no': row['goods_return_deliver_detail_ref_warehouse_record__deliver_detail__main__no']
+        }
+        data.append(item)
+    return data

+ 0 - 0
apps/plan/__init__.py


+ 36 - 0
apps/plan/filters.py

@@ -0,0 +1,36 @@
+#coding=utf-8
+import django_filters
+from apps.base import clean_datetime_range
+from models import ProductionPlan, SalePlan
+
+
+class ProductionPlanFilter(django_filters.FilterSet):
+    no = django_filters.CharFilter(name='no',lookup_expr='icontains')
+    name = django_filters.CharFilter(name='name', lookup_expr='icontains')
+    status = django_filters.CharFilter(name='status')
+    create_user_text = django_filters.CharFilter(name='create_user__name', lookup_expr='icontains')
+
+    class Meta:
+        model = ProductionPlan
+        fields = (
+            'no', 'name', 'status', 'create_user_text'
+        )
+
+    def __init__(self, data=None, *args, **kwargs):
+        data = clean_datetime_range(data, 'create_time')
+        super(ProductionPlanFilter, self).__init__(data, *args, **kwargs)
+
+
+
+class SalePlanFilter(django_filters.FilterSet):
+    no = django_filters.CharFilter(name='no',lookup_expr='icontains')
+    customer_text = django_filters.CharFilter(name='customer__name', lookup_expr='icontains')
+    create_time = django_filters.DateTimeFromToRangeFilter(field_name='create_time')
+
+    class Meta:
+        model = SalePlan
+        fields = "__all__"
+
+    def __init__(self, data=None, *args, **kwargs):
+        data = clean_datetime_range(data, 'create_time', 'source')
+        super(SalePlanFilter, self).__init__(data, *args, **kwargs)

+ 0 - 0
apps/plan/migrations/__init__.py


+ 200 - 0
apps/plan/models.py

@@ -0,0 +1,200 @@
+# coding=utf-8
+from django.db.models import Sum
+from django.utils import timezone
+from apps.foundation.models import Option
+from apps.customer.models import Customer
+from apps.exceptions import CustomError
+from django.db import models
+from django.conf import settings
+from apps.account.models import User, Department
+from apps.goods.models import Goods
+
+
+class SalePlan(models.Model):
+    no = models.CharField(max_length=20, verbose_name=u"单号", editable=False)
+    customer = models.ForeignKey(Customer, verbose_name=u"客户", on_delete=models.PROTECT)
+    status = models.PositiveSmallIntegerField(choices=settings.CHECK_STATUS_CHOICES, verbose_name=u"状态", default=settings.DEFAULT)
+    notes = models.CharField(max_length=200, verbose_name=u"备注", blank=True, null=True)
+    create_time = models.DateTimeField(verbose_name=u"创建时间", default=timezone.now)
+    create_user = models.ForeignKey(User, verbose_name=u"创建人", related_name='sale_plan_ref_create_user', on_delete=models.PROTECT, blank=True, editable=False)
+    department = models.ForeignKey(Department, verbose_name=u"创建部门", editable=False, on_delete=models.PROTECT)
+    check_time = models.DateTimeField(verbose_name=u"审核时间", blank=True, null=True)
+    check_user = models.ForeignKey(User, verbose_name=u"审核人", related_name='sale_plan_ref_check_user', on_delete=models.PROTECT, blank=True, null=True)
+    total_count = models.BigIntegerField(verbose_name=u'合计数量', default=0)
+    total_amount = models.BigIntegerField(verbose_name=u'合计金额', null=True, blank=True)
+    class Meta:
+        db_table = "sale_plan"
+        verbose_name = u"销售计划管理"
+        ordering = ('-id',)
+        index_together = (
+            'create_time', 'check_time', 'status'
+        )
+        unique_together = (
+            'no',
+        )
+        default_permissions = ()
+        permissions = (
+            ("view_sale_plan", u"浏览"),
+            ("add_sale_plan", u"添加"),
+            ("check_sale_plan", u"审核"),
+            ("delete_sale_plan", u"删除"),
+            ("export_sale_plan", u"导出"),
+            ("print_sale_plan", u"打印"),
+        )
+
+    @staticmethod
+    def getById(id):
+        instance = SalePlan.objects.filter(pk=id).first()
+        if not instance:
+            raise CustomError(u'未找到相应的销售计划')
+        return instance
+
+    def save(self, *args, **kwargs):
+        if self.no == None or self.no == '':
+            now = timezone.now()
+            rows = SalePlan.objects.filter(create_time__gte=now.strftime('%Y-%m-%d')).order_by('-no')
+            count = rows.count()
+            if count == 0:
+                self.no = 'XS%s%03d' % (now.strftime('%Y%m%d'), count + 1)
+            else:
+                self.no = rows[0].no[:2] + str(int(rows[0].no[2:]) + 1)
+        super(SalePlan, self).save(*args, **kwargs)
+
+    def update_total(self):
+        total_count = 0
+        total_amount = None
+        sum_row = SalePlanDetail.objects.filter(main=self).aggregate(total_count=Sum('require_count'), total_amount=Sum('amount'))
+        if sum_row:
+            total_count = sum_row['total_count'] or 0
+            total_amount = sum_row['total_amount']
+        self.total_count = total_count
+        self.total_amount = total_amount
+        self.save()
+
+
+class SalePlanDetail(models.Model):
+    main = models.ForeignKey(SalePlan, verbose_name=u'销售计划', on_delete=models.PROTECT)
+    goods = models.ForeignKey(Goods, verbose_name=u'成品', on_delete=models.PROTECT)
+    quality_request = models.ForeignKey(Option, verbose_name=u"质量要求", blank=True, null=True, on_delete=models.PROTECT)
+    require_time = models.DateTimeField(verbose_name=u"需求时间", blank=True, null=True)
+    require_count = models.BigIntegerField(verbose_name=u'需求数量', default=0)
+    price = models.BigIntegerField(verbose_name=u'单价', null=True, blank=True)
+    amount = models.BigIntegerField(verbose_name=u'金额', null=True, blank=True)
+
+    class Meta:
+        db_table = "sale_plan_detail"
+        verbose_name = u"出库查询"
+        ordering = ('-id',)
+        index_together = (
+            'require_time',
+        )
+        default_permissions = ()
+        permissions = (# 销售计划明细
+            ("view_material_deliver_query", u"浏览"),
+            ("export_material_deliver_query", u"导出"),
+            ("print_material_deliver_query", u"打印"),
+        )
+
+    @staticmethod
+    def getById(id):
+        instance = SalePlanDetail.objects.filter(pk=id).first()
+        if not instance:
+            raise CustomError(u'未找到相应的销售计划明细')
+        return instance
+
+
+class ProductionPlan(models.Model):
+    no = models.CharField(max_length=20, verbose_name=u"单号", editable=False)
+    name = models.CharField(max_length=100, verbose_name=u"名称", blank=True)
+    status = models.PositiveSmallIntegerField(choices=settings.CHECK_STATUS_CHOICES, verbose_name=u"审核状态", default=settings.DEFAULT)
+    create_user = models.ForeignKey(User, verbose_name=u"创建人", related_name='production_plan_ref_create_user', on_delete=models.PROTECT, blank=True, editable=False)
+    department = models.ForeignKey(Department, verbose_name=u"所属部门", blank=True, editable=False, on_delete=models.PROTECT)
+    create_time = models.DateTimeField(verbose_name=u"创建时间",  default=timezone.now)
+    check_user = models.ForeignKey(User, verbose_name=u"审核人",related_name='production_plan_ref_check_user', on_delete=models.PROTECT, null=True, blank=True)
+    check_time = models.DateTimeField(verbose_name=u"审核时间", null=True)
+    total_count = models.BigIntegerField(u"合计数量", default=0)
+    notes = models.CharField(max_length=100, verbose_name=u"备注", null=True, blank=True)
+
+    check_user2 = models.ForeignKey(User, verbose_name=u"复核人",related_name='production_plan_ref_check_user2', on_delete=models.PROTECT, null=True, blank=True)
+    check_time2 = models.DateTimeField(verbose_name=u"复核时间", null=True)
+
+    check_user3 = models.ForeignKey(User, verbose_name=u"批准人",related_name='production_plan_ref_check_user3', on_delete=models.PROTECT, null=True, blank=True)
+    check_time3 = models.DateTimeField(verbose_name=u"批准时间", null=True)
+
+    class Meta:
+        db_table = "production_plan"
+        verbose_name = u"生产计划管理"
+        ordering = ('-id',)
+        index_together = (
+            'name',
+            'create_time','status','check_time'
+        )
+        unique_together = (
+            'no',
+        )
+        default_permissions = ()
+        permissions = (
+            ("view_production_plan", u"浏览"),
+            ("add_production_plan", u"添加"),
+            ("check_production_plan", u"审核"),
+            ("delete_production_plan", u"删除"),
+            ("export_production_plan", u"导出"),
+            ("print_production_plan", u"打印"),
+            ("check2_production_plan", u"复核"),
+            ("check3_production_plan", u"批准"),
+        )
+
+    def updateTotalCount(self):
+        sum_count = 0
+        sum_rows = ProductionPlanDetail.objects.filter(production=self).aggregate(sum_count=Sum('count'))
+        if sum_rows:
+            sum_count = sum_rows['sum_count'] or 0
+        self.total_count = sum_count
+        self.save()
+
+    @staticmethod
+    def getById(id):
+        instance = ProductionPlan.objects.filter(pk=id).first()
+        if not instance:
+            raise CustomError(u'未找到相应的生产计划')
+        return instance
+
+    def save(self, *args, **kwargs):
+        if self.no == None or self.no == '':
+            now = timezone.now()
+            rows = ProductionPlan.objects.filter(create_time__gte=now.strftime('%Y-%m-%d')).order_by('-no')
+            count = rows.count()
+            if count == 0:
+                self.no = 'SC%s%03d' % (now.strftime('%Y%m%d'), count + 1)
+            else:
+                self.no = rows[0].no[:2] + str(int(rows[0].no[2:]) + 1)
+        super(ProductionPlan, self).save(*args, **kwargs)
+
+
+class ProductionPlanDetail(models.Model):
+    production = models.ForeignKey(ProductionPlan, verbose_name=u"生产计划", on_delete=models.PROTECT)
+    product = models.ForeignKey(Goods, verbose_name=u"产品", on_delete=models.PROTECT)
+    quality_request = models.ForeignKey(Option, verbose_name=u"质量要求", on_delete=models.PROTECT, null=True)
+    count = models.BigIntegerField(u"数量", default=0)
+    product_user = models.ForeignKey(User, verbose_name=u"负责人", on_delete=models.PROTECT, null=True)
+    p_department = models.ForeignKey(Department, verbose_name=u"生产车间", related_name='production_detail_ref_p_department', on_delete=models.PROTECT, blank=True, null=True)
+    department = models.ForeignKey(Department, verbose_name=u"负责人部门", related_name='production_detail_ref_department', on_delete=models.PROTECT, null=True, blank=True)
+    product_time = models.DateTimeField(verbose_name=u"生产时间", null=True)
+    notes = models.CharField(max_length=100, verbose_name=u"备注", null=True, blank=True)
+
+    class Meta:
+        db_table = "production_plan_detail"
+        verbose_name = u"出库查询"
+        default_permissions = ()
+        permissions = (# 生产计划明细
+            ("view_consumable_deliver_query", u"浏览"),
+            ("export_consumable_deliver_query", u"导出"),
+            ("print_consumable_deliver_query", u"打印"),
+        )
+
+    @staticmethod
+    def getById(id):
+        instance = ProductionPlanDetail.objects.filter(pk=id)
+        if not instance:
+            raise CustomError(u'未找到相应的生产计划明细')
+        return instance

+ 95 - 0
apps/plan/resources.py

@@ -0,0 +1,95 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+from import_export import resources
+from import_export.fields import Field
+
+class ProductionPlanResource(resources.Resource):
+
+    def __init__(self):
+        super(ProductionPlanResource, self).__init__()
+        self.fields['no'] = Field(attribute='no')
+        self.fields['name'] = Field(attribute='name')
+        self.fields['total_count'] = Field(attribute='total_count')
+        self.fields['create_user_text'] = Field(attribute='create_user_text')
+        self.fields['create_time'] = Field(attribute='create_time')
+        self.fields['status_text'] = Field(attribute='status_text')
+        self.fields['check_time'] = Field(attribute='check_time')
+        self.fields['check_user_text'] = Field(attribute='check_user_text')
+        self.fields['notes'] = Field(attribute='notes')
+
+    def get_export_headers(self):
+        return [u'单号', u'名称', u'合计数量', u'创建时间', u'创建人', u'审核状态', u'审核时间', u'审核人', u'备注',]
+
+    class Meta:
+        fields = ('no', 'name', 'total_count', 'create_time', 'create_user_text', 'status_text', 'check_time', 'check_user_text',
+                  'notes',)
+        export_order = fields
+
+
+class ProductionPlanDetailResource(resources.Resource):
+
+    def __init__(self):
+        super(ProductionPlanDetailResource, self).__init__()
+        self.fields['name'] = Field(attribute='name')
+        self.fields['model'] = Field(attribute='model')
+        self.fields['quality_request_text'] = Field(attribute='quality_request_text')
+        self.fields['count'] = Field(attribute='count')
+        self.fields['p_department_text'] = Field(attribute='p_department_text')
+        self.fields['product_time'] = Field(attribute='product_time')
+        self.fields['product_user_text'] = Field(attribute='product_user_text')
+        self.fields['notes'] = Field(attribute='notes')
+
+    def get_export_headers(self):
+        return [u'产品名称', u'产品代码', u'质量要求', u'数量', u'生产车间', u'生产时间', u'负责人', u'备注',]
+
+    class Meta:
+        fields = ('name', 'model', 'quality_request_text', 'count', 'p_department_text', 'product_time',
+                  'product_user_text', 'notes',)
+        export_order = fields
+
+
+class SalePlanResource(resources.Resource):
+
+    def __init__(self):
+        super(SalePlanResource, self).__init__()
+        self.fields['no'] = Field(attribute='no')
+        self.fields['create_time'] = Field(attribute='create_time')
+        self.fields['customer_text'] = Field(attribute='customer_text')
+        self.fields['goods_text'] = Field(attribute='goods_text')
+        self.fields['total_count'] = Field(attribute='total_count')
+        self.fields['total_amount'] = Field(attribute='total_amount')
+        self.fields['status_text'] = Field(attribute='status_text')
+        self.fields['create_user_text'] = Field(attribute='create_user_text')
+        self.fields['department_text'] = Field(attribute='department_text')
+        self.fields['check_user_text'] = Field(attribute='check_user_text')
+        self.fields['check_time'] = Field(attribute='check_time')
+        self.fields['notes'] = Field(attribute='notes')
+
+    def get_export_headers(self):
+        return [u'单号', u'客户', u'产品', u'合计数量', u'合计金额', u'创建时间', u'创建人', u'所属部门', u'审核状态', u'审核时间', u'审核人', u'备注']
+
+    class Meta:
+        fields = ('no', 'customer_text', 'goods_text','total_count', 'total_amount', 'create_time', 'create_user_text', 'department_text', 'status_text',
+                  'check_time', 'check_user_text', 'notes')
+        export_order = fields
+
+class SalePlanDetailResource(resources.Resource):
+
+    def __init__(self):
+        super(SalePlanDetailResource, self).__init__()
+        self.fields['goods_text'] = Field(attribute='goods_text')
+        self.fields['goods_model'] = Field(attribute='goods_model')
+        self.fields['quality_request_text'] = Field(attribute='quality_request_text')
+        self.fields['require_count'] = Field(attribute='require_count')
+        self.fields['price'] = Field(attribute='price')
+        self.fields['require_time'] = Field(attribute='require_time')
+        self.fields['current_count'] = Field(attribute='current_count')
+        self.fields['require_production_count'] = Field(attribute='require_production_count')
+
+    def get_export_headers(self):
+        return [u'产品名称', u'产品代码', u'质量要求', u'数量', u'单价', u'需求时间', u'当前库存', u'需生产']
+
+    class Meta:
+        fields = ('goods_text', 'goods_model', 'quality_request_text', 'require_count', 'price', 'require_time', 'current_count',
+                  'require_production_count')
+        export_order = fields

+ 255 - 0
apps/plan/serializers.py

@@ -0,0 +1,255 @@
+#coding=utf-8
+from django.conf import settings
+from rest_framework import serializers
+
+from apps import base
+from apps.base import Formater
+from apps.exceptions import CustomError
+from apps.foundation.models import BizLog
+from apps.serializer_errors import dump_serializer_errors
+from libs.booleancharfield import CountShowCharField, PriceShowCharField, AmountShowCharField, EmptyPriceShowCharField, \
+    EmptyAmountShowCharField
+from models import ProductionPlan, SalePlan, ProductionPlanDetail, SalePlanDetail
+
+
+class ProductionPlanSerializer(serializers.ModelSerializer):
+    status = serializers.CharField(read_only=True)
+    status_text = serializers.CharField(source='get_status_display', read_only=True)
+    create_user_text = serializers.CharField(source='create_user.name', read_only=True)
+    total_count = CountShowCharField(read_only=True)
+    department_text = serializers.CharField(source='department.name', read_only=True)
+    create_time = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    check_user_text = serializers.CharField(source='check_user.name', read_only=True)
+    check_time = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+
+    check_user_text2 = serializers.CharField(source='check_user2.name', read_only=True)
+    check_time2 = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    check_user_text3 = serializers.CharField(source='check_user3.name', read_only=True)
+    check_time3 = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    class Meta:
+        model = ProductionPlan
+        fields = '__all__'
+
+    @staticmethod
+    def factory(user,data, id=None):
+        if id:
+            instance = ProductionPlan.getById(id)
+        else:
+            instance = None
+        serializer = ProductionPlanSerializer(instance, data=data)
+        serializer.user = user
+        return serializer
+
+    def create(self, validated_data):
+        validated_data['create_user'] = self.user
+        validated_data['department'] = self.user.department
+        production = ProductionPlan.objects.create(**validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加生产计划[%s],id=%d" % (production.name, production.id),
+            validated_data
+        )
+        return production
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+    def update(self, instance, validated_data):
+        instance = super(ProductionPlanSerializer, self).update(instance, validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.UPDATE,
+            u"修改生产计划[%s],id=%d" % (instance.name, instance.id),
+            validated_data
+        )
+        return instance
+
+
+class SalePlanSerializer(serializers.ModelSerializer):
+    status = serializers.CharField(read_only=True)
+    no = serializers.CharField(read_only=True)
+    status_text = serializers.CharField(source='get_status_display', read_only=True)
+    create_user_text = serializers.CharField(source='create_user.name', read_only=True)
+    department_text = serializers.CharField(source='department.name', read_only=True)
+    create_time = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    check_user_text = serializers.CharField(source='check_user.name', read_only=True)
+    check_time = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    customer_text = serializers.CharField(source='customer.name', read_only=True)
+    goods_text = serializers.SerializerMethodField()
+    total_count = CountShowCharField(read_only=True)
+    total_amount = EmptyAmountShowCharField(read_only=True)
+
+    class Meta:
+        model = SalePlan
+        fields = '__all__'
+
+    @staticmethod
+    def factory(user, data, id=None):
+        if id:
+            instance = SalePlan.getById(id)
+        else:
+            instance = None
+        serializer = SalePlanSerializer(instance, data=data)
+        serializer.user = user
+        return serializer
+
+
+    def create(self, validated_data):
+        validated_data['create_user'] = self.user
+        validated_data['department'] = self.user.department
+        instance = SalePlan.objects.create(**validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加销售计划[%s],id=%d" % (instance.no, instance.id),
+            validated_data
+        )
+        return instance
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+
+    def update(self, instance, validated_data):
+        instance = super(SalePlanSerializer, self).update(instance, validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.UPDATE,
+            u"修改销售计划[%s],id=%d" % (instance.no, instance.id),
+            validated_data
+        )
+        return instance
+
+    def get_goods_text(self, obj):
+        data = []
+        rows = SalePlanDetail.objects.filter(main=obj)
+        data.extend([row.goods.product_base.name for row in rows])
+        return ','.join(data)
+
+
+class ProductionPlanDetailSerializer(serializers.ModelSerializer):
+    name = serializers.CharField(source='product.product_base.name', read_only=True)
+    model = serializers.CharField(source='product.product_base.model', read_only=True)
+    count = CountShowCharField()
+    quality_request_text = serializers.CharField(source='quality_request.name', read_only=True)
+    product_user_id = serializers.CharField(source='product_user.id', read_only=True)
+    p_department_id = serializers.CharField(source='p_department.id', read_only=True)
+    department_id = serializers.CharField(source='department.id', read_only=True)
+    product_user_text = serializers.CharField(source='product_user.name', read_only=True)
+    p_department_text = serializers.CharField(source='p_department.name', read_only=True)
+    department_text = serializers.CharField(source='department.name', read_only=True)
+
+    class Meta:
+        model = ProductionPlanDetail
+        fields = '__all__'
+
+    @staticmethod
+    def factory(user, data, id=None):
+        if id:
+            instances = ProductionPlanDetail.getById(id)
+        else:
+            instances = None
+        serializer = ProductionPlanDetailSerializer(instances, data=data)
+        serializer.user = user
+        return serializer
+
+    def create(self, validated_data):
+        production = ProductionPlanDetail.objects.create(**validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加生产计划明细[%s],id=%d" % (production.product.product_base.name, production.id),
+            validated_data
+        )
+        return production
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+
+    def validate(self, data):
+        if 'count' in data:
+            data['count'] = Formater.formatCount(data['count'])
+        data['department'] = None
+        if 'product_user' in data and data['product_user']:
+            data['department'] = data['product_user'].department
+        return data
+
+    def update(self, instance, validated_data):
+        instance = super(ProductionPlanDetailSerializer, self).update(instance, validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.UPDATE,
+            u"修改生产计划明细[%s],id=%d" % (instance.name, instance.id),
+            validated_data
+        )
+        return instance
+
+
+class SalePlanDetailSerializer(serializers.ModelSerializer):
+    status = serializers.CharField(read_only=True)
+    goods_text = serializers.CharField(source='goods.product_base.name', read_only=True)
+    goods_model = serializers.CharField(source='goods.product_base.model', read_only=True)
+    require_time_text = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    quality_request_text = serializers.CharField(source='quality_request.name', read_only=True)
+    quality_request_id = serializers.CharField(source='quality_request.id', read_only=True)
+    current_count = serializers.CharField(source='goods.product_base.stock_count', read_only=True)
+    require_production_count = serializers.SerializerMethodField()
+    require_count = CountShowCharField()
+    price = EmptyPriceShowCharField(allow_blank=True)
+    amount = EmptyAmountShowCharField(read_only=True)
+
+
+    class Meta:
+        model = SalePlanDetail
+        fields = '__all__'
+
+    @staticmethod
+    def factory(user, data):
+        instance = None
+        serializer = SalePlanDetailSerializer(instance, data=data)
+        serializer.user = user
+        return serializer
+
+    def create(self, validated_data):
+        instance = SalePlanDetail.objects.create(**validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加销售计划明细[%s],id=%d" % (instance.goods, instance.id),
+            validated_data
+        )
+        return instance
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+    def get_require_production_count(self, obj):
+        require_production_count = 0
+        if obj.require_count > obj.goods.product_base.stock_count:
+            require_production_count = obj.require_count-obj.goods.product_base.stock_count
+        return Formater.formatCountShow(require_production_count)
+
+    def validate(self, data):
+        if 'require_count' in data:
+            data['require_count'] = Formater.formatCount(data['require_count'])
+        if 'price' in data:
+            if data['price'] == '':
+                data['price'] = None
+            else:
+                data['price'] = Formater.formatPrice(data['price'])
+                data['amount'] = data['require_count'] * data['price']
+        return data

+ 24 - 0
apps/plan/urls.py

@@ -0,0 +1,24 @@
+#coding=utf-8
+
+from django.conf.urls import url
+
+from views import *
+
+urlpatterns = (
+    url(r'^production/data/$', production_list),
+    url(r'^production/export/$', production_export),
+    url(r'^production/save/$', production_save),
+    url(r'^production/check/$', production_check),
+    url(r'^production/delete/$', production_delete),
+    url(r'^production/detail/$', production_detail),
+
+    url(r'^plan/options/$', plan_options), # 返回生产计划销售计划页面下列框信息
+
+    url(r'^sale/data/$', sale_list),
+    url(r'^sale/save/$', sale_save),
+    url(r'^sale/check/$', sale_check),
+    url(r'^sale/delete/$', sale_delete),
+    url(r'^sale/detail/$', sale_detail),
+    url(r'^sale/export_detail/$', sale_export_detail),
+    url(r'^sale/export/$', sale_export),
+)

+ 518 - 0
apps/plan/views.py

@@ -0,0 +1,518 @@
+# coding=utf-8
+import traceback
+import json
+
+from _mysql_exceptions import IntegrityError
+from django.db.models import ProtectedError, Q, Sum
+from django.utils import timezone
+from django.views.decorators.csrf import csrf_exempt
+from django.db import transaction, IntegrityError
+from apps.account.decorators import token_required, permission_required
+from apps.account.models import Department, User
+from apps.base import Formater
+from apps.foundation.models import BizLog, Option
+from apps.goods.models import Goods
+from apps.plan.filters import ProductionPlanFilter, SalePlanFilter
+from apps.plan.models import ProductionPlan, SalePlan, ProductionPlanDetail, SalePlanDetail
+from apps.plan.resources import ProductionPlanResource, SalePlanResource, SalePlanDetailResource, ProductionPlanDetailResource
+from apps.plan.serializers import ProductionPlanSerializer, ProductionPlanDetailSerializer, SalePlanSerializer, \
+    SalePlanDetailSerializer
+
+from libs.http import JSONResponse, JSONError, DataGridJSONResponse
+from libs import utils
+from apps.exceptions import CustomError, ExportChange
+from django.conf import settings
+
+
+@csrf_exempt
+@permission_required('plan.view_production_plan')
+def production_list(request):
+    department_ids = request.user.getSubDepartmentIds()
+    user_ids = request.user.getSubEmployeeIds()
+    rows = ProductionPlan.objects.filter(Q(create_user_id__in=user_ids) | Q(department_id__in=department_ids) | Q(create_user=request.user))
+    f = ProductionPlanFilter(request.GET, queryset=rows)
+    total_row = f.qs.aggregate(total_count=Sum('total_count'))
+    more = {
+        'total_count': Formater.formatCountShow(total_row['total_count'])
+    }
+    rows, total = utils.get_page_data(request, f.qs)
+    serializer = ProductionPlanSerializer(rows, many=True)
+    return DataGridJSONResponse(serializer.data, total, more)
+
+@csrf_exempt
+@permission_required('plan.export_production_plan')
+def production_export(request):
+    id = request.GET.get('id')
+    if id:
+        production = ProductionPlan.getById(id)
+        goods_rows = ProductionPlanDetail.objects.filter(production__id=production.id)
+        serializer = ProductionPlanDetailSerializer(goods_rows, many=True)
+        export_data = ExportChange.dict_to_obj(serializer)
+        export_data = ProductionPlanDetailResource().export(export_data)
+        filename = utils.attachment_save(export_data)
+        BizLog.objects.addnew(request.user, BizLog.EXPORT, u"导出生产计划明细")
+        return JSONResponse({'filename': filename})
+
+    department_ids = request.user.getSubDepartmentIds()
+    user_ids = request.user.getSubEmployeeIds()
+    rows = ProductionPlan.objects.filter(Q(create_user_id__in=user_ids) | Q(department_id__in=department_ids) | Q(create_user=request.user))
+    f = ProductionPlanFilter(request.GET, queryset=rows)
+    serializer = ProductionPlanSerializer(f.qs, many=True)
+    export_data = ExportChange.dict_to_obj(serializer)
+    export_data = ProductionPlanResource().export(export_data)
+    filename = utils.attachment_save(export_data)
+    BizLog.objects.addnew(request.user, BizLog.EXPORT, u"导出生产计划")
+    return JSONResponse({'filename': filename})
+
+@csrf_exempt
+@permission_required('plan.add_production_plan')
+def production_save(request):
+    id = request.GET.get('id')
+    goods_data = json.loads(request.POST.get('items'))
+    production_data = json.loads(request.POST.get('production'))
+    try:
+        with transaction.atomic():
+            pb = ProductionPlanSerializer.factory(request.user, production_data, id)
+            if pb.instance and pb.instance.status == settings.PASS:
+                raise CustomError(u'审核通过,禁止修改')
+            pb = pb.validSave()
+            ProductionPlanDetail.objects.filter(production=pb).delete()
+            for good_data in goods_data:
+                good_data['production'] = pb.id
+                good_data['product'] = good_data['id']
+                if 'product_time' in good_data and not good_data['product_time']:
+                    good_data['product_time'] = None
+                production = ProductionPlanDetailSerializer.factory(request.user, data=good_data)
+                production.validSave()
+            pb.updateTotalCount()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'保存失败!')
+    return JSONResponse()
+
+@csrf_exempt
+@permission_required('plan.check_production_plan')
+def production_check(request):
+    id = request.GET.get('id')
+    c_type = request.GET.get('c_type')
+    status = int(request.GET.get('status'))
+    try:
+        with transaction.atomic():
+            production = ProductionPlan.getById(id)
+            if c_type == 'check':
+                # 审核
+                if status == settings.PASS:
+                    if production.status == settings.CHECKING:
+                        raise CustomError(u'该生产计划已审核')
+                    production.status = settings.CHECKING
+                    production.check_user = request.user
+                    production.check_time = timezone.now()
+                    BizLog.objects.addnew(
+                        request.user,
+                        BizLog.CHECK,
+                        u"审核生产计划[%s],id=%d" % (production.name, production.id),
+                    )
+                else:
+                    if production.status == settings.DEFAULT:
+                        raise CustomError(u'该生产计划未审核')
+                    if production.check_user2:
+                        raise CustomError(u'该生产计划已复核')
+                    production.status = settings.DEFAULT
+                    production.check_user = None
+                    production.check_time = None
+                    BizLog.objects.addnew(
+                        request.user,
+                        BizLog.CHECK,
+                        u"取消审核生产计划[%s],id=%d" % (production.name, production.id),
+                    )
+            elif c_type == 'check2':
+                #复核
+                if status == settings.PASS:
+                    if production.status == settings.DEFAULT:
+                        raise CustomError(u'该生产计划未审核')
+                    if production.check_user3:
+                        raise CustomError(u'该生产计划已批准')
+
+                    production.check_user2 = request.user
+                    production.check_time2 = timezone.now()
+                    BizLog.objects.addnew(
+                        request.user,
+                        BizLog.CHECK,
+                         u"复核生产计划[%s],id=%d" % (production.name, production.id),
+                    )
+                else:
+                    if not production.check_user2:
+                        raise CustomError(u'该生产计划未复核')
+                    if production.check_user3:
+                        raise CustomError(u'该生产计划已批准')
+
+                    production.check_user2 = None
+                    production.check_time2 = None
+                    BizLog.objects.addnew(
+                        request.user,
+                        BizLog.CHECK,
+                         u"取消复核生产计划[%s],id=%d" % (production.name, production.id),
+                    )
+            elif c_type == 'check3':
+                #批准
+                if status == settings.PASS:
+                    if not production.check_user2:
+                        raise CustomError(u'该生产计划未复核')
+                    if production.check_user3:
+                        raise CustomError(u'该生产计划已批准')
+                    production.status = status
+                    production.check_user3 = request.user
+                    production.check_time3 = timezone.now()
+                    BizLog.objects.addnew(
+                        request.user,
+                        BizLog.CHECK,
+                        u"批准生产计划[%s],id=%d" % (production.name, production.id),
+                    )
+                else:
+                    if not production.check_user3:
+                        raise CustomError(u'该生产计划未批准')
+
+                    production.status = settings.CHECKING
+                    production.check_user3 = None
+                    production.check_time3 = None
+                    BizLog.objects.addnew(
+                        request.user,
+                        BizLog.CHECK,
+                        u"撤消批准生产计划[%s],id=%d" % (production.name, production.id),
+                    )
+            production.save()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'审核失败')
+    return JSONResponse({})
+
+@csrf_exempt
+@permission_required('plan.delete_production_plan')
+def production_delete(request):
+    id = int(request.GET.get('id'))
+    try:
+        with transaction.atomic():
+            production = ProductionPlan.getById(id)
+            if production.status == settings.PASS:
+                raise CustomError(u'该生产计划已审核,禁止删除')
+            BizLog.objects.addnew(request.user, BizLog.DELETE, u"删除生产计划[%s],id=%d" % (production.name, production.id))
+            ProductionPlanDetail.objects.filter(production__id=production.id).delete()
+            production.delete()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except ProtectedError:
+        return JSONError(u'该计划已被引用,禁止删除')
+    except IntegrityError:
+        return JSONError(u'该计划已被引用,禁止删除')
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'删除失败!')
+
+    return JSONResponse({})
+
+@csrf_exempt
+@permission_required('plan.view_production_plan')
+def production_detail(request):
+    id = request.GET.get('id')
+    production = ProductionPlan.getById(id)
+    company = production.department.getCompany()
+    goods_rows = ProductionPlanDetail.objects.filter(production__id=production.id)
+    data = {
+        'production': [],
+        'goods_items': []
+    }
+    if production.status == settings.PASS:
+        status_text = u'已审核'
+    else:
+        status_text = u'待审核'
+    production_item = {
+        'id': production.id,
+        'name': production.name,
+        'company': company.name,
+        'no': production.no,
+        'total_count': Formater.formatCountShow(production.total_count),
+        'status': production.status,
+        'status_text': status_text,
+        'notes': production.notes,
+        'create_user': production.create_user.name,
+        'check_user': production.check_user and production.check_user.name or '',
+        'check_time': Formater.formatStrTime(production.check_time),
+        'check_user2': production.check_user2 and production.check_user2.name or '',
+        'check_time2': Formater.formatStrTime(production.check_time2),
+        'check_user3': production.check_user3 and production.check_user3.name or '',
+        'check_time3': Formater.formatStrTime(production.check_time3),
+        'create_time': Formater.formatStrTime(production.create_time),
+    }
+    data['production'].append(production_item)
+    for row in goods_rows:
+        item = {
+            'id': row.id,
+            'goods_id': row.product.id,
+            'name': row.product.product_base.name,
+            'model': row.product.product_base.model,
+            'unit': row.product.product_base.unit,
+            'quality_request_id': row.quality_request and row.quality_request.id,
+            'quality_request_text': row.quality_request and row.quality_request.name or '',
+            'count': Formater.formatCountShow(row.count),
+            'product_user_id': row.product_user and row.product_user.id,
+            'product_user_text': row.product_user and row.product_user.name or '',
+            'p_department_id': row.p_department and row.p_department.id,
+            'p_department_text': row.p_department and row.p_department.name or '',
+            'product_time': Formater.formatStrDate(row.product_time),
+            'notes': row.notes or ''
+        }
+        data['goods_items'].append(item)
+    return JSONResponse(data)
+
+@csrf_exempt
+@token_required
+def plan_options(request):
+    departments = Department.objects.filter().values('id', 'name')
+    users = User.objects.filter(status=User.INSERVICE).values('id', 'name', 'department__name')
+    data = {
+        'users': [],
+        'departments': [],
+    }
+    for row in users:
+        data['users'].append(
+            {'id':row['id'], 'name':row['name'], 'department_name':row['department__name']}
+        )
+    for row in departments:
+        data['departments'].append(
+            {'id':row['id'], 'name':row['name']}
+        )
+    return JSONResponse(data)
+
+@csrf_exempt
+@permission_required('plan.view_sale_plan')
+def sale_list(request):
+    department_ids = request.user.getSubDepartmentIds()
+    user_ids = request.user.getSubEmployeeIds()
+    rows = SalePlan.objects.filter(Q(create_user_id__in=user_ids) | Q(department_id__in=department_ids) | Q(create_user=request.user))
+    f = SalePlanFilter(request.GET, queryset=rows)
+    total_row = f.qs.aggregate(total_count=Sum('total_count'), total_amount=Sum('total_amount'))
+    more = {
+        'total_count' : Formater.formatCountShow(total_row['total_count']),
+        'total_amount' : Formater.formatAmountShow(total_row['total_amount'])
+    }
+    rows, total = utils.get_page_data(request, f.qs)
+    serializer = SalePlanSerializer(rows, many=True)
+    return DataGridJSONResponse(serializer.data, total, more)
+
+
+@csrf_exempt
+@permission_required('plan.add_sale_plan')
+def sale_save(request):
+    source = request.GET.get('source')
+    id = request.GET.get('id')
+    # detail_id:添加保存,销售计划直接保存,不需要添加明细detail_id=-1,添加明细保存默认detail_id=0,修改保存,detail_id=明细id
+    detail_id = -1
+    if source == 'touch':
+        goods_data = json.loads(request.body)['item']
+        sale_data = json.loads(request.body)['main']
+        detail_id = json.loads(request.body)['detail']
+    else:
+        goods_data = json.loads(request.POST.get('goods'))
+        sale_data = json.loads(request.POST.get('sale'))
+    try:
+        with transaction.atomic():
+            serializer = SalePlanSerializer.factory(request.user, sale_data, id)
+            if serializer.instance and serializer.instance.status == settings.PASS:
+                raise CustomError(u'该销售计划已审核,禁止修改!')
+            serializer = serializer.validSave()
+            if source == 'touch' and detail_id >= 0:
+                # 手机端保存,如果是修改,先删除要修改的明细
+                SalePlanDetail.objects.filter(id=int(detail_id)).delete()
+                for item in goods_data:
+                    amount = Formater.formatPrice(item['price'])*Formater.formatCount(item['require_count']) if item['price'] else 0
+                    require_time = item['require_time'] and item['require_time'].split('T')[0] or None
+                    instance = SalePlanDetail.objects.create(
+                        main_id=id,
+                        goods_id=item['goods'],
+                        quality_request_id=item['quality_request'],
+                        require_count=Formater.formatCount(item['require_count']),
+                        require_time=require_time,
+                        amount=amount,
+                        price=Formater.formatPrice(item['price'] or 0),
+                    )
+                    BizLog.objects.addnew(
+                        request.user,
+                        BizLog.INSERT,
+                         u"添加销售计划明细[%s],id=%d" % (instance.goods, instance.id),
+                        item
+                    )
+            else:
+                SalePlanDetail.objects.filter(main=serializer).delete()
+
+                for good_data in goods_data:
+                    good_data['main'] = serializer.id
+                    if 'require_time' in good_data and not good_data['require_time']:
+                        good_data['require_time'] = None
+                    detail_serializer = SalePlanDetailSerializer.factory(request.user, data=good_data)
+                    detail_serializer.validSave()
+            serializer.update_total()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'保存失败!')
+
+    return JSONResponse(serializer.id)
+
+@csrf_exempt
+@permission_required('plan.delete_sale_plan')
+def sale_delete(request):
+    id = int(request.GET.get('id'))
+    try:
+        with transaction.atomic():
+            sale_plan = SalePlan.getById(id)
+            if sale_plan.status == settings.PASS:
+                raise CustomError(u'该销售计划已审核,禁止删除!')
+
+            BizLog.objects.addnew(request.user, BizLog.DELETE, u"删除销售计划[%s],id=%d" % (sale_plan.no, sale_plan.id))
+            SalePlanDetail.objects.filter(main=sale_plan).delete()
+            sale_plan.delete()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except ProtectedError:
+        return JSONError(u'该销售计划已被引用,禁止删除!')
+    except IntegrityError:
+        return JSONError(u'该销售计划已被引用,禁止删除!')
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'删除失败!')
+
+    return JSONResponse({})
+
+@csrf_exempt
+@permission_required('plan.view_sale_plan')
+def sale_detail(request):
+    id = request.GET.get('id')
+    sale_plan = SalePlan.getById(id)
+    if sale_plan.status == settings.PASS:
+        check_status = u'已审核'
+    else:
+        check_status = u'待审核'
+    company = sale_plan.department.getCompany()
+    sale_data = {
+        'id': sale_plan.id,
+        'no': sale_plan.no,
+        'company': company.name,
+        'total_count': Formater.formatCountShow(sale_plan.total_count),
+        'total_amount': Formater.formatAmountShow(sale_plan.total_amount),
+        'name': sale_plan.customer.name,
+        'mobile': sale_plan.customer.mobile,
+        'customer_id': sale_plan.customer.id,
+        'company_name': sale_plan.customer.company_name or '',
+        'credit_code': sale_plan.customer.credit_code or '',
+        'company_tel': sale_plan.customer.company_tel or '',
+        'address': sale_plan.customer.address or '',
+        'status': check_status,
+        'status_id': sale_plan.status,
+        'check_user': sale_plan.check_user and sale_plan.check_user.name or '',
+        'create_user': sale_plan.create_user and sale_plan.create_user.name or '',
+        'check_time':sale_plan.check_time and Formater.formatStrTime(sale_plan.check_time) or '',
+        'create_time': Formater.formatStrTime(sale_plan.create_time),
+        'notes': sale_plan.notes or '',
+    }
+
+    data = {
+        'goods_items': [],
+        'sale_data': sale_data
+    }
+    is_toproduction = False
+    goods_rows = SalePlanDetail.objects.filter(main=sale_plan)
+    for row in goods_rows:
+        require_product_count = 0
+        if row.require_count > row.goods.product_base.stock_count:
+            require_product_count = row.require_count-row.goods.product_base.stock_count
+            is_toproduction = True
+        item = {
+            'detail_id': row.id,
+            'id': row.id,
+            'goods_id': row.goods.id,
+            'name': row.goods.product_base.name,
+            'model': row.goods.product_base.model,
+            'unit': row.goods.product_base.unit,
+            'quality_request': row.quality_request and row.quality_request.id or None,
+            'quality_request_text':row.quality_request and row.quality_request.name or '',
+            'require_count': Formater.formatCountShow(row.require_count),
+            'price': Formater.formatEmptyPriceShow(row.price),
+            'require_time': Formater.formatStrDate(row.require_time),
+            'current_stock_count': Formater.formatCountShow(row.goods.product_base.stock_count),
+            'require_product_count': Formater.formatCountShow(require_product_count),
+        }
+        data['goods_items'].append(item)
+    data['sale_data']['is_toproduction'] = is_toproduction
+    return JSONResponse(data)
+
+@csrf_exempt
+@permission_required('plan.check_sale_plan')
+def sale_check(request):
+    id = request.GET.get('id')
+    status = int(request.GET.get('status'))
+    try:
+        with transaction.atomic():
+            sale_plan = SalePlan.getById(id)
+            if status == settings.PASS:
+                if sale_plan.status == settings.PASS:
+                    raise CustomError(u'该销售计划已审核')
+                sale_plan.status = status
+                sale_plan.check_user = request.user
+                sale_plan.check_time = timezone.now()
+                BizLog.objects.addnew(
+                    request.user,
+                    BizLog.CHECK,
+                    u"审核销售计划[%s],id=%d" % (sale_plan.no, sale_plan.id),
+                )
+            else:
+                if sale_plan.status == settings.DEFAULT:
+                    raise CustomError(u'该销售计划未审核')
+                sale_plan.status = status
+                sale_plan.check_user = None
+                sale_plan.check_time = None
+                BizLog.objects.addnew(
+                    request.user,
+                    BizLog.CHECK,
+                    u"取消审核销售计划[%s],id=%d" % (sale_plan.no, sale_plan.id),
+                )
+            sale_plan.save()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'审核失败')
+    return JSONResponse({})
+
+@csrf_exempt
+@permission_required('plan.export_sale_plan')
+def sale_export_detail(request):
+    id = request.GET.get('id')
+    sale_plan = SalePlan.getById(id)
+    sale_plan_detail = SalePlanDetail.objects.filter(main=sale_plan)
+    serializer = SalePlanDetailSerializer(sale_plan_detail, many=True)
+    export_data = ExportChange.dict_to_obj(serializer)
+    export_data = SalePlanDetailResource().export(export_data)
+    filename = utils.attachment_save(export_data)
+    BizLog.objects.addnew(request.user, BizLog.EXPORT, u"导出销售计划[%s]明细" % (serializer.no))
+    return JSONResponse({'filename': filename})
+
+
+@csrf_exempt
+@permission_required('plan.export_sale_plan')
+def sale_export(request):
+    department_ids = request.user.getSubDepartmentIds()
+    user_ids = request.user.getSubEmployeeIds()
+    rows = SalePlan.objects.filter(
+        Q(create_user_id__in=user_ids) | Q(department_id__in=department_ids) | Q(create_user=request.user))
+    f = SalePlanFilter(request.GET, queryset=rows)
+    serializer = SalePlanSerializer(f.qs, many=True)
+    export_data = ExportChange.dict_to_obj(serializer)
+    export_data = SalePlanResource().export(export_data)
+    filename = utils.attachment_save(export_data)
+    BizLog.objects.addnew(request.user, BizLog.EXPORT, u"导出销售计划")
+    return JSONResponse({'filename': filename})

+ 0 - 0
apps/product/__init__.py


+ 7 - 0
apps/product/manager.py

@@ -0,0 +1,7 @@
+#coding=utf-8
+
+from django.db.models import Manager
+
+
+class ProductBaseManager(Manager):
+    pass

+ 0 - 0
apps/product/migrations/__init__.py


+ 77 - 0
apps/product/models.py

@@ -0,0 +1,77 @@
+#coding=utf-8
+
+from django.db import models
+from django.utils import timezone
+
+from apps.exceptions import CustomError
+from apps.foundation.models import Option
+from apps.product.manager import ProductBaseManager
+
+
+class ProductBase(models.Model):
+    MATERIAL = 0
+    CONSUMABLE = 1
+    GOODS = 2
+    TYPE_CHOICES = (
+        (MATERIAL,u'原料'),
+        (CONSUMABLE,u'耗材'),
+        (GOODS, u'成品'),
+    )
+    TYPE_CHOICES_DICT = [{'id':x, 'value':y} for x, y in TYPE_CHOICES]
+
+    type = models.PositiveSmallIntegerField(choices=TYPE_CHOICES, verbose_name=u"类型")
+    option_type = models.ForeignKey(Option, verbose_name=u"类别", null=True, blank=True, on_delete=models.PROTECT)
+    name = models.CharField(max_length=100, verbose_name=u"名称")
+    model = models.CharField(max_length=100, verbose_name=u"代码")
+    barcode = models.CharField(max_length=100, verbose_name=u"条码", null=True, blank=True)
+    unit = models.CharField(max_length=10, verbose_name=u"单位", null=True, blank=True)
+    code = models.CharField(max_length=100, verbose_name=u"助记码", null=True, blank=True)
+    standard = models.CharField(max_length=100, verbose_name=u"规格", null=True, blank=True)
+    warehouse_place = models.CharField(max_length=100, verbose_name=u"存放库位", null=True, blank=True)
+    notes = models.CharField(max_length=500, verbose_name=u"备注", null=True, blank=True)
+    enabled = models.BooleanField(verbose_name=u"在用", default=True)
+    create_time = models.DateTimeField(verbose_name=u"创建时间", default=timezone.now)
+    stock_count = models.BigIntegerField(verbose_name=u"库存数量", default=0)
+    stock_amount = models.BigIntegerField(verbose_name=u"库存金额", default=0)
+    stock_amount2 = models.BigIntegerField(verbose_name=u"库存金额2", default=0)
+    avg_cost_price = models.BigIntegerField(verbose_name=u"平均成本价", default=0)
+    avg_cost_price2 = models.BigIntegerField(verbose_name=u"平均成本价2", default=0)
+    max_price = models.BigIntegerField(verbose_name=u'最高入库价', default=0)
+    min_price = models.BigIntegerField(verbose_name=u'最低入库价', default=0)
+    last_entry_price = models.BigIntegerField(verbose_name=u"最新入库价", default=0)
+    last_entry_time = models.DateTimeField(verbose_name=u"最后入库时间", null=True, blank=True)
+    last_deliverd_time = models.DateTimeField(verbose_name=u"最后出库时间", null=True, blank=True)
+    objects = ProductBaseManager()
+
+    class Meta:
+        db_table = "product_base"
+        verbose_name = u"其他"
+        ordering = ('-id',)
+        index_together = (
+            'name',
+            'model',
+        )
+        default_permissions = ()
+
+        permissions = (# 产品
+            ("view_material_stock_ledger", u"原料总账查看"),
+            ("export_material_stock_ledger", u"原料总账导出"),
+            ("view_consumable_stock_ledger", u"耗材总账查看"),
+            ("export_consumable_stock_ledger", u"耗材总账导出"),
+            ("view_goods_stock_ledger", u"成品总账查看"),
+            ("export_goods_stock_ledger", u"成品总账导出"),
+            ("view_department_ledger", u"部门总账查看"),
+            ("export_department_ledger", u"部门总账导出"),
+            ("view_goods_product", u"库存查询"),
+        )
+
+    @staticmethod
+    def getTypeText(type):
+        return ProductBase.TYPE_CHOICES[type][1]
+
+    @staticmethod
+    def getById(id):
+        instance = ProductBase.objects.filter(pk=id).first()
+        if not instance:
+            raise CustomError(u'未找到相应的产品')
+        return instance

+ 52 - 0
apps/product/serializers.py

@@ -0,0 +1,52 @@
+#coding=utf-8
+
+from rest_framework import serializers
+from django.db.models import Q
+
+from apps.exceptions import CustomError
+from apps.serializer_errors import dump_serializer_errors
+from apps.warehouse.models import WarehouseStock
+from models import ProductBase
+
+class ProductBaseSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = ProductBase
+        fields = '__all__'
+
+    @staticmethod
+    def factory(user, data, id=None):
+        if id:
+            instance = ProductBase.getById(id)
+        else:
+            instance = None
+
+        serializer = ProductBaseSerializer(instance, data=data)
+        serializer.user = user
+        return serializer
+
+    def create(self, validated_data):
+        item = ProductBase.objects.filter(type=validated_data['type'], model=validated_data['model']).first()
+        if item:
+            raise CustomError(u'代码为[%s]的%s已存在' % (validated_data['model'],ProductBase.getTypeText(validated_data['type'])))
+
+        instance = super(ProductBaseSerializer, self).create(validated_data)
+        WarehouseStock.createByProduct(instance)
+        return instance
+
+    def update(self, instance, validated_data):
+        if 'type' in validated_data:
+            if validated_data['type'] != instance.type:
+                raise CustomError(u'禁止修改产品的类型')
+
+        item = ProductBase.objects.filter(Q(type=instance.type), Q(model=validated_data['model']),~Q(id=instance.id)).first()
+        if item:
+            raise CustomError(u'代码为[%s]的%s已存在' % (validated_data['model'], ProductBase.getTypeText(instance.type)))
+
+        instance = super(ProductBaseSerializer, self).update(instance, validated_data)
+        return instance
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))

+ 0 - 0
apps/purchase/__init__.py


+ 153 - 0
apps/purchase/filters.py

@@ -0,0 +1,153 @@
+#coding=utf-8
+import django_filters
+from django.conf import settings
+from django.db.models import Q
+from models import PurchasePlan, PurchaseOrder, PurchaseUser, GodownEntry, PurchasePayment, PurchaseOrderDetail, \
+    GodownEntryReturn, GodownEntryReturnDetail, PurchasePrice
+from apps.base import clean_datetime_range
+
+
+
+class PurchasePlanFilter(django_filters.FilterSet):
+    no = django_filters.CharFilter(name='no', lookup_expr='icontains')
+    name = django_filters.CharFilter(name='name', lookup_expr='icontains')
+    notes = django_filters.CharFilter(name='notes', lookup_expr='icontains')
+
+    class Meta:
+        model = PurchasePlan
+        fields = ('no', 'name', 'status', 'notes')
+
+    # def filter_status(self, queryset, *args):
+    #     if args[1]:
+    #         value = int(args[1])
+    #         if value != 3:
+    #             queryset = queryset.filter(status=value)
+    #         else:
+    #             queryset = queryset.filter(~Q(status=settings.PASS))
+    #     return queryset
+
+
+class PurchasePriceFilter(django_filters.FilterSet):
+    name = django_filters.CharFilter(name='purchase_detail__product__name', lookup_expr='icontains')
+    model = django_filters.CharFilter(name='purchase_detail__product__model', lookup_expr='icontains')
+
+    class Meta:
+        model = PurchaseUser
+        fields = "__all__"
+
+
+class PurchaseOrderFilter(django_filters.FilterSet):
+    create_time = django_filters.DateTimeFromToRangeFilter(field_name='create_time')
+    no = django_filters.CharFilter(name='no', lookup_expr='icontains')
+    supplier_name = django_filters.CharFilter(name='supplier__name', lookup_expr='icontains')
+    status = django_filters.CharFilter(method='filter_status')
+    notes = django_filters.CharFilter(name='notes', lookup_expr='icontains')
+
+    class Meta:
+        model = PurchaseOrder
+        fields = (
+            'create_time', 'no', 'supplier_name', 'status', 'arrval', 'notes', )
+
+    def __init__(self, data=None, *args, **kwargs):
+        data = clean_datetime_range(data, 'create_time','source')
+        super(PurchaseOrderFilter, self).__init__(data, *args, **kwargs)
+
+    def filter_status(self, queryset, *args):
+        if args[1]:
+            value = int(args[1])
+            if value != 3:
+                queryset = queryset.filter(status=value)
+            else:
+                queryset = queryset.filter(~Q(status=PurchaseOrder.TAKE_EFFECT))
+        return queryset
+
+class PurchaseOrderDetailFilter(django_filters.FilterSet):
+    supplier_name = django_filters.CharFilter(name='main__supplier__name', lookup_expr='icontains')
+    order_no = django_filters.CharFilter(name='main__no', lookup_expr='icontains')
+
+    class Meta:
+        model = PurchaseOrderDetail
+        fields = ('order_no', 'supplier_name', 'check_status',)
+
+    def __init__(self, data=None, *args, **kwargs):
+        data = clean_datetime_range(data, 'create_time')
+        super(PurchaseOrderDetailFilter, self).__init__(data, *args, **kwargs)
+
+
+class GodownEntryFilter(django_filters.FilterSet):
+    no = django_filters.CharFilter(name='no', lookup_expr='icontains')
+    supplier_text = django_filters.CharFilter(name='supplier__name', lookup_expr='icontains')
+    purchase_order_no = django_filters.CharFilter(name='purchase_order__no', lookup_expr='icontains')
+    create_user_text = django_filters.CharFilter(name='create_user__name', lookup_expr='icontains')
+    create_time = django_filters.DateTimeFromToRangeFilter(field_name='create_time')
+    check_time = django_filters.DateTimeFromToRangeFilter(field_name='check_time')
+    class Meta:
+        model = GodownEntry
+        fields = "__all__"
+    
+    def __init__(self, data=None, *args, **kwargs):
+        data = clean_datetime_range(data, 'create_time', 'source')
+        data = clean_datetime_range(data, 'check_time')
+        super(GodownEntryFilter, self).__init__(data, *args, **kwargs)
+
+
+class GodownEntryReturnFilter(django_filters.FilterSet):
+    no = django_filters.CharFilter(name='no', lookup_expr='icontains')
+    supplier_text = django_filters.CharFilter(name='supplier__name', lookup_expr='icontains')
+    create_user_text = django_filters.CharFilter(name='create_user__name', lookup_expr='icontains')
+    create_time = django_filters.DateTimeFromToRangeFilter(field_name='create_time')
+    check_time = django_filters.DateTimeFromToRangeFilter(field_name='check_time')
+
+    class Meta:
+        model = GodownEntryReturn
+        fields = "__all__"
+
+    def __init__(self, data=None, *args, **kwargs):
+        data = clean_datetime_range(data, 'create_time', 'source')
+        data = clean_datetime_range(data, 'check_time')
+        super(GodownEntryReturnFilter, self).__init__(data, *args, **kwargs)
+
+
+class GodownEntryReturnDetailFilter(django_filters.FilterSet):
+    main_no = django_filters.CharFilter(name='main__no', lookup_expr='icontains')
+    godownentry_no = django_filters.CharFilter(name='godownentry_detail__main__no', lookup_expr='icontains')
+    main_supplier = django_filters.CharFilter(name='main__supplier__name', lookup_expr='icontains')
+    main_create_user = django_filters.CharFilter(name='main__create_user__name', lookup_expr='icontains')
+    main_check_user = django_filters.CharFilter(name='main__check_user__name', lookup_expr='icontains')
+    main_create_time = django_filters.DateTimeFromToRangeFilter(field_name='main__create_time')
+    main_check_time = django_filters.DateTimeFromToRangeFilter(field_name='main__check_time')
+    notes = django_filters.CharFilter(name='notes', lookup_expr='icontains')
+
+    class Meta:
+        model = GodownEntryReturnDetail
+        fields = "__all__"
+
+    def __init__(self, data=None, *args, **kwargs):
+        data = clean_datetime_range(data, 'main__create_time')
+        data = clean_datetime_range(data, 'main__check_time')
+        super(GodownEntryReturnDetailFilter, self).__init__(data, *args, **kwargs)
+
+
+class PurchasePaymentFilter(django_filters.FilterSet):
+    create_time = django_filters.DateTimeFromToRangeFilter(field_name='create_time')
+    no = django_filters.CharFilter(name='no', lookup_expr='icontains')
+    order_no = django_filters.CharFilter(name='order__no', lookup_expr='icontains')
+    supplier_name = django_filters.CharFilter(name='order__supplier__name', lookup_expr='icontains')
+    notes = django_filters.CharFilter(name='notes', lookup_expr='icontains')
+
+    class Meta:
+        model = PurchasePayment
+        fields = (
+            'create_time', 'no', 'supplier_name', 'status', 'order_no', 'notes', )
+
+    def __init__(self, data=None, *args, **kwargs):
+        data = clean_datetime_range(data, 'create_time','source')
+        super(PurchasePaymentFilter, self).__init__(data, *args, **kwargs)
+
+
+class PurchasePriceExportFilter(django_filters.FilterSet):
+    supplier_name = django_filters.CharFilter(name='supplier__name', lookup_expr='icontains')
+
+    class Meta:
+        model = PurchasePrice
+        fields = "__all__"

+ 0 - 0
apps/purchase/migrations/__init__.py


+ 862 - 0
apps/purchase/models.py

@@ -0,0 +1,862 @@
+# coding=utf-8
+from django.utils import timezone
+from apps.foundation.models import Option
+from apps.exceptions import CustomError
+from django.db import models
+from django.conf import settings
+from django.db.models import Sum, Q, Count
+from apps.account.models import User, Department
+from apps.supplier.models import Supplier
+from apps.product.models import ProductBase
+from apps.warehouse.models import Warehouse, WarehouseStock, WarehouseStockRecord, WarehouseRecord
+from libs.filefield import PathAndRename
+
+
+class PurchasePlan(models.Model):
+    no = models.CharField(max_length=20, verbose_name=u"单号", editable=False)
+    name = models.CharField(max_length=100, verbose_name=u"名称")
+    status = models.PositiveSmallIntegerField(choices=settings.CHECK_STATUS_CHOICES, verbose_name=u"审核状态", default=settings.DEFAULT)
+    create_user = models.ForeignKey(User, verbose_name=u"创建人", related_name='purchase_plan_ref_create_user',
+                                    on_delete=models.PROTECT, blank=True, editable=False)
+    department = models.ForeignKey(Department, verbose_name=u"创建部门", related_name='purchase_plan_ref_department', blank=True, editable=False, on_delete=models.PROTECT)
+    demend_user = models.ForeignKey(User, verbose_name=u"需求人", related_name='purchase_plan_ref_demend_user', on_delete=models.PROTECT, blank=True, null=True)
+    demend_department = models.ForeignKey(Department, verbose_name=u"需求车间", related_name='purchase_plan_ref_demend_department', on_delete=models.PROTECT, null=True, blank=True)
+    create_time = models.DateTimeField(verbose_name=u"下单时间",  default=timezone.now)
+    check_user = models.ForeignKey(User, verbose_name=u"审核人",related_name='purchase_plan_ref_check_user', on_delete=models.PROTECT, null=True)
+    check_time = models.DateTimeField(verbose_name=u"审核时间", null=True)
+    total_count = models.BigIntegerField(verbose_name=u"合计数量", default=0)
+    notes = models.CharField(max_length=100, verbose_name=u"备注", null=True, blank=True)
+
+    # check_user2 = models.ForeignKey(User, verbose_name=u"复核人", related_name='purchase_plan_ref_check_user2',
+    #                                 on_delete=models.PROTECT, null=True, blank=True)
+    # check_time2 = models.DateTimeField(verbose_name=u"复核时间", null=True)
+    #
+    # check_user3 = models.ForeignKey(User, verbose_name=u"批准人", related_name='purchase_plan_ref_check_user3',
+    #                                 on_delete=models.PROTECT, null=True, blank=True)
+    # check_time3 = models.DateTimeField(verbose_name=u"批准时间", null=True)
+    class Meta:
+        db_table = "purchase_plan"
+        verbose_name = u"计划管理"
+        ordering = ('-id',)
+        index_together = (
+            'name',
+            'create_time','status','check_time'
+        )
+        unique_together = (
+            'no',
+        )
+        default_permissions = ()
+        permissions = (# 采购计划管理
+            ("view_purchase_plan", u"浏览"),
+            ("add_purchase_plan", u"添加"),
+            ("check_purchase_plan", u"审核"),
+            ("delete_purchase_plan", u"删除"),
+            ("export_purchase_plan", u"导出"),
+            ("print_purchase_plan", u"打印"),
+            ("export_purchase_plan_price", u"导出询价汇总"),
+            ("add_purchase_user_plan", u"添加采购员"),
+            ("check2_purchase_plan", u"复核"),
+            ("check3_purchase_plan", u"批准"),
+        )
+
+    def isReport(self):
+        purchaseprcies = PurchasePrice.objects.filter(purchase_detail__purchase=self, report=True).count()
+        if purchaseprcies:
+            return True
+        return False
+
+    def isCompart(self):
+        purchaseprcies = PurchasePlanDetail.objects.filter(purchase=self, is_compact=True).count()
+        if purchaseprcies:
+            return True
+        return False
+
+    def updateTotalCount(self):
+        total_count = 0
+        sum_row = PurchasePlanDetail.objects.filter(purchase=self).aggregate(Sum('purchase_count'))
+        if sum_row:
+            total_count = sum_row['purchase_count__sum'] or 0
+        self.total_count = total_count
+        self.save()
+
+    def getPurchaseDetails(self):
+        return PurchasePlanDetail.objects.filter(purchase=self)
+
+    @staticmethod
+    def getById(id):
+        instance = PurchasePlan.objects.filter(pk=id).first()
+        if not instance:
+            raise CustomError(u'未找到相应的采购计划')
+        return instance
+
+    def save(self, *args, **kwargs):
+        if self.no == None or self.no == '':
+            now = timezone.now()
+            rows = PurchasePlan.objects.filter(create_time__gte=now.strftime('%Y-%m-%d')).order_by('-no')
+            count = rows.count()
+            if count == 0:
+                self.no = 'CG%s%03d' % (now.strftime('%Y%m%d'), count + 1)
+            else:
+                self.no = rows[0].no[:2] + str(int(rows[0].no[2:]) + 1)
+        super(PurchasePlan, self).save(*args, **kwargs)
+
+    def removeCompact(self):
+        PurchasePlanDetail.objects.filter(purchase=self).update(is_compact=False)
+
+    def updateCompact(self):
+        self.removeCompact()
+        product_ids = PurchaseOrderDetail.objects.filter(main__plan=self).values_list('product_id',flat=True)
+        PurchasePlanDetail.objects.filter(purchase=self,product_id__in=product_ids).update(is_compact=True)
+
+
+class PurchasePlanDetail(models.Model):
+    purchase = models.ForeignKey(PurchasePlan, verbose_name=u"采购计划", on_delete=models.PROTECT)
+    product = models.ForeignKey(ProductBase, verbose_name=u"产品", on_delete=models.PROTECT)
+    #quality_request = models.ForeignKey(Option, verbose_name=u"质量要求", null=True, blank=True, on_delete=models.PROTECT)
+    quality_request_text = models.CharField(max_length=100, verbose_name=u"质量要求", null=True, blank=True)
+    purchase_count = models.BigIntegerField(u"采购数量", default=0)
+    product_time = models.DateTimeField(verbose_name=u"需求时间", null=True, blank=True)
+    notes = models.CharField(max_length=100, verbose_name=u"备注", null=True, blank=True)
+    is_compact = models.BooleanField(verbose_name=u"生成采购合同", default=False)
+
+    class Meta:
+        db_table = "purchase_plan_detail"
+        verbose_name = u"退料查询"
+        default_permissions = ()
+        permissions = (# 采购计划明细
+            ("view_consumable_deliver_return_query", u"浏览"),
+            ("export_consumable_deliver_return_query", u"导出"),
+            ("print_consumable_deliver_return_query", u"打印"),
+        )
+
+    @staticmethod
+    def getById(id):
+        instance = PurchasePlanDetail.objects.filter(id=id).first()
+        if not instance:
+            raise CustomError(u'未找到相应的采购计划明细')
+        return instance
+
+class PurchaseUser(models.Model):
+    purchase = models.ForeignKey(PurchasePlan, verbose_name=u"采购计划", on_delete=models.PROTECT)
+    purchase_user = models.ForeignKey(User, verbose_name=u"采购员", on_delete=models.PROTECT, blank=True)
+
+    class Meta:
+        db_table = "purchase_user"
+        verbose_name = u"询价管理"
+        default_permissions = ()
+        permissions = (
+            ("view_purchase_user_plan", u"浏览"),
+            ("add_purchase_price_plan", u"上报价格"),
+            ("edit_purchase_price", u"修改询价"),
+        )
+
+
+class PurchasePrice(models.Model):
+    purchase_detail = models.ForeignKey(PurchasePlanDetail, verbose_name=u"采购计划明细", on_delete=models.PROTECT, blank=True)
+    purchase_user = models.ForeignKey(User, verbose_name=u"采购员", on_delete=models.PROTECT, blank=True)
+    report = models.BooleanField(verbose_name=u"上报", default=False)
+    supplier = models.ForeignKey(Supplier, verbose_name=u"供应商", on_delete=models.PROTECT, null=True)
+    price = models.BigIntegerField(u"价格", default=0)
+    tax_rate = models.FloatField(verbose_name=u"税率", null=True, blank=True)
+    notes = models.CharField(max_length=100, verbose_name=u"备注", null=True, blank=True)
+
+    class Meta:
+        db_table = "purchase_plan_price"
+        verbose_name = u"入库查询"
+        ordering = ('-id',)
+        default_permissions = ()
+        permissions = (# 询价记录
+            ("view_material_godownentry_query", u"浏览"),
+            ("export_material_godownentry_query", u"导出"),
+            ("print_material_godownentry_query", u"打印"),
+        )
+
+    @staticmethod
+    def getById(id):
+        instances = PurchasePrice.objects.filter(pk=id).first()
+        if not instances:
+            raise CustomError(u'未找到相应的询价记录')
+        return instances
+
+
+class PurchaseOrder(models.Model):
+    DRAFT = 0
+    TAKE_EFFECT = 1
+    CHECKING = 2
+    STATUS_CHOICES = (
+        (DRAFT, u'草拟'),
+        (TAKE_EFFECT, u'生效'),
+        (CHECKING, u'审核中'),
+    )
+
+    NO_ARRVAL = 0
+    PART_ARRVAL = 1
+    ALL_ARRVAL = 2
+    ARRVAL_CHOICES = (
+        (NO_ARRVAL, u'未到货'),
+        (PART_ARRVAL, u'部分到货'),
+        (ALL_ARRVAL, u'全部到货'),
+    )
+
+    no = models.CharField(max_length=50, verbose_name=u"合同号",  null=True)
+    order_no = models.CharField(max_length=50, verbose_name=u"单号", editable=False)
+    supplier = models.ForeignKey(Supplier, verbose_name=u"供应商", on_delete=models.PROTECT)
+    count = models.BigIntegerField(u'数量', default=0)
+    amount = models.BigIntegerField(u'金额', default=0)
+    apply_amount = models.BigIntegerField(verbose_name=u'申请金额', default=0)
+    plan = models.ForeignKey(PurchasePlan, verbose_name=u"采购计划", on_delete=models.PROTECT, blank=True,null=True)
+    status = models.PositiveSmallIntegerField(choices=STATUS_CHOICES, verbose_name=u"状态", default=DRAFT)
+    arrval = models.PositiveSmallIntegerField(choices=ARRVAL_CHOICES, verbose_name=u"到货", default=NO_ARRVAL)
+    notes = models.CharField(max_length=200, verbose_name=u"备注", blank=True, null=True)
+    create_time = models.DateTimeField(verbose_name=u"创建时间", default=timezone.now)
+    create_user = models.ForeignKey(User, related_name='purchase_order_ref_create_user', verbose_name=u"创建人", on_delete=models.PROTECT, editable=False)
+    department = models.ForeignKey(Department, verbose_name=u"创建部门", editable=False, on_delete=models.PROTECT)
+    check_time = models.DateTimeField(verbose_name=u"审核时间", null=True)
+    check_user = models.ForeignKey(User, related_name='purchase_order_ref_check_user', verbose_name=u"审核人", on_delete=models.PROTECT, null=True)
+    payment_type = models.CharField(max_length=50, verbose_name=u"付款方式", null=True, blank=True)
+    deliver_time = models.CharField(max_length=200, verbose_name=u"交货时间", blank=True, null=True)
+
+    check_user2 = models.ForeignKey(User, verbose_name=u"复核人", related_name='purchase_order_ref_check_user2',
+                                    on_delete=models.PROTECT, null=True, blank=True)
+    check_time2 = models.DateTimeField(verbose_name=u"复核时间", null=True)
+
+    check_user3 = models.ForeignKey(User, verbose_name=u"批准人", related_name='purchase_order_ref_check_user3',
+                                    on_delete=models.PROTECT, null=True, blank=True)
+    check_time3 = models.DateTimeField(verbose_name=u"批准时间", null=True)
+    consignee_name = models.CharField(max_length=100, verbose_name=u"收货人姓名", null=True)
+    consignee_tel = models.CharField(max_length=100, verbose_name=u"收货人电话", null=True)
+    class Meta:
+        db_table = "purchase_order"
+        verbose_name = u"合同管理"
+        ordering = ('-id',)
+        index_together = (
+            'create_time', 'check_time', 'status', 'no'
+        )
+        unique_together = (
+            'order_no',
+        )
+        default_permissions = ()
+        permissions = (# 采购合同管理
+            ("view_purchase_order", u"浏览"),
+            ("add_purchase_order", u"添加"),
+            ("edit_purchase_order", u"修改"),
+            ("senior_purchase_order", u"高级修改"),
+            ("check_purchase_order", u"审核"),
+            ("delete_purchase_order", u"删除"),
+            ("export_purchase_order", u"导出"),
+            ("print_purchase_order", u"打印"),
+            ("check2_purchase_order", u"复核"),
+            ("check3_purchase_order", u"批准"),
+        )
+
+    def updateApplyAmount(self):
+        sum_amount = 0
+        sum_rows = PurchasePayment.objects.filter(order=self).aggregate(sum_amount=Sum('apply_amount'))
+        if sum_rows:
+            sum_amount = sum_rows['sum_amount'] or 0
+
+        self.apply_amount = sum_amount
+        self.save()
+
+    def save(self, *args, **kwargs):
+        if self.order_no == None or self.order_no == '':
+            now = timezone.now()
+            rows = PurchaseOrder.objects.filter(create_time__gte=now.strftime('%Y-%m-%d')).order_by('-order_no')
+            count = rows.count()
+            if count == 0:
+                self.order_no = 'CN%s%03d' % (now.strftime('%Y%m%d'), count + 1)
+            else:
+                self.order_no = rows[0].order_no[:2] + str(int(rows[0].order_no[2:]) + 1)
+        super(PurchaseOrder, self).save(*args, **kwargs)
+
+    @staticmethod
+    def getById(id):
+        instance = PurchaseOrder.objects.filter(pk=id).first()
+        if not instance:
+            raise CustomError(u'未找到相应的采购合同')
+        return instance
+
+    def updateAmount(self):
+        sum_count = 0
+        sum_amount = 0
+
+        sum_row = PurchaseOrderDetail.objects.filter(main=self).aggregate(sum_count=Sum('count'), sum_amount=Sum('amount'))
+        if sum_row:
+            sum_count = sum_row['sum_count'] or 0
+            sum_amount = sum_row['sum_amount'] or 0
+
+        self.count = sum_count
+        self.amount = sum_amount
+        self.save()
+
+    def updateArrval(self):
+        rows = PurchaseOrderDetail.objects.filter(main=self)
+        for row in rows:
+            arrval_count = 0
+            g_rows = GodownEntryDetail.objects.filter(product_base_id=row.product_id, main__purchase_order=self, main__status=settings.PASS).aggregate(sum_count=Sum('count'))
+            if g_rows:
+                arrval_count = g_rows['sum_count'] or 0
+            row.arrval_count = arrval_count
+            if row.arrval_count >= row.count:
+                row.is_arrval = True
+            else:
+                row.is_arrval = False
+            row.save()
+
+        sum_arrval_count = 0
+        d_rows = PurchaseOrderDetail.objects.filter(main=self).aggregate(sum_count=Sum('arrval_count'))
+        if d_rows:
+            sum_arrval_count = d_rows['sum_count'] or 0
+
+        if sum_arrval_count == 0:
+            self.arrval = PurchaseOrder.NO_ARRVAL
+            self.save()
+        else:
+            all_arrval = True
+            for row in rows:
+                if not row.is_arrval:
+                    all_arrval = False
+                    break
+
+            if all_arrval:
+                self.arrval = PurchaseOrder.ALL_ARRVAL
+            else:
+                self.arrval = PurchaseOrder.PART_ARRVAL
+            self.save()
+
+path_and_rename = PathAndRename("purchase_order/invoice/")
+class PurchaseOrderDetail(models.Model):
+    main = models.ForeignKey(PurchaseOrder, verbose_name=u'采购合同', on_delete=models.PROTECT)
+    product = models.ForeignKey(ProductBase, verbose_name=u'产品', on_delete=models.PROTECT)
+    #quality_request = models.ForeignKey(Option, verbose_name=u"质量标准", null=True, on_delete=models.PROTECT)
+    quality_request_text = models.CharField(max_length=100, verbose_name=u"质量要求", null=True, blank=True)
+    count = models.BigIntegerField(u'数量', default=0)
+    price = models.BigIntegerField(u'单价', default=0)
+    amount = models.BigIntegerField(u'金额', default=0)
+    arrval_count = models.BigIntegerField(u'到货数量', default=0)
+    is_arrval = models.BooleanField(verbose_name=u"全部到货", default=0)
+    invoice_no = models.CharField(max_length=50, verbose_name=u"发票号", null=True)
+    invoice_amount = models.BigIntegerField(u'发票金额', null=True)
+    invoice_date = models.DateField(verbose_name=u"开票日期", null=True)
+    create_time = models.DateTimeField(verbose_name=u"录入时间", null=True)
+    create_user = models.ForeignKey(User, related_name='purchase_order_detail_ref_create_user', verbose_name=u"录入人",
+                                   on_delete=models.PROTECT, null=True)
+    department = models.ForeignKey(Department, verbose_name=u"录入部门", null=True, on_delete=models.PROTECT)
+    check_time = models.DateTimeField(verbose_name=u"审核时间", null=True)
+    check_user = models.ForeignKey(User, related_name='purchase_order_detail_ref_check_user', verbose_name=u"审核人",
+                                   on_delete=models.PROTECT, null=True)
+    check_status = models.PositiveSmallIntegerField(choices=settings.CHECK_STATUS_CHOICES,
+                                              verbose_name=u"审核状态", default=settings.DEFAULT)
+
+    class Meta:
+        db_table = "purchase_order_detail"
+        verbose_name = u"发票管理"
+        ordering = ('-id',)
+        default_permissions = ()
+        permissions = (# 采购合同明细
+            ("view_purchase_invoice", u"浏览"),
+            ("add_purchase_invoice", u"录入"),
+            ("check_purchase_invoice", u"审核"),
+            ("export_purchase_invoice", u"导出"),
+        )
+
+    @staticmethod
+    def getById(id):
+        instances = PurchaseOrderDetail.objects.filter(id=id).first()
+        if not instances:
+            raise CustomError(u'未找到相应的合同明细')
+        return instances
+
+class PurchaseInvoiceImage(models.Model):
+    order_detail = models.ForeignKey(PurchaseOrderDetail, related_name='invoice_image_ref_purchase_order_detail', verbose_name=u"合同明细",
+                                    editable=False)
+    invoice_image = models.ImageField(verbose_name=u"发票图片", default='', null=True, upload_to=path_and_rename,
+                                      blank=True)
+    class Meta:
+        db_table = "purchase_invoice_image"
+        verbose_name = u"采购合同发票图片"
+
+class PurchasePayment(models.Model):
+    no = models.CharField(max_length=20, verbose_name=u"单号", editable=False)
+    order = models.ForeignKey(PurchaseOrder, verbose_name=u"合同", on_delete=models.PROTECT)
+    status = models.PositiveSmallIntegerField(choices=settings.CHECK_STATUS_CHOICES, default=settings.DEFAULT, verbose_name=u"状态")
+    amount = models.BigIntegerField(verbose_name=u'合同金额', default=0)
+    actual_amount = models.BigIntegerField(verbose_name=u'实付金额', default=0)
+    apply_amount = models.BigIntegerField(verbose_name=u'申请金额', default=0)
+    is_pay = models.BooleanField(verbose_name=u"是否付款", default=0)
+    notes = models.CharField(max_length=200, verbose_name=u"申请备注", blank=True, null=True)
+    create_time = models.DateTimeField(verbose_name=u"创建时间", default=timezone.now)
+    create_user = models.ForeignKey(User, related_name='purchase_payment_ref_create_user', verbose_name=u"创建人", on_delete=models.PROTECT, editable=False)
+    department = models.ForeignKey(Department, verbose_name=u"创建部门", editable=False, on_delete=models.PROTECT)
+    check_time = models.DateTimeField(verbose_name=u"审核时间", null=True)
+    check_user = models.ForeignKey(User, related_name='purchase_payment_ref_check_user', verbose_name=u"审核人", on_delete=models.PROTECT, null=True)
+    review_user = models.ForeignKey(User, verbose_name=u"复核人", related_name='purchase_payment_ref_review_user',
+                                    on_delete=models.PROTECT, null=True, blank=True)
+    review_time = models.DateTimeField(verbose_name=u"复核时间", null=True)
+
+    ratify_user = models.ForeignKey(User, verbose_name=u"批准人", related_name='purchase_payment_ref_ratify_user',
+                                    on_delete=models.PROTECT, null=True, blank=True)
+    ratify_time = models.DateTimeField(verbose_name=u"批准时间", null=True)
+
+    class Meta:
+        db_table = "purchase_payment"
+        verbose_name = u"付款管理"
+        ordering = ('-id',)
+        index_together = (
+            'create_time', 'check_time', 'status'
+        )
+        unique_together = (
+            'no',
+        )
+        default_permissions = ()
+        permissions = (
+            ("view_purchase_payment", u"浏览"),
+            ("add_purchase_payment", u"添加"),
+            ("check_purchase_payment", u"审核"),
+            ("review_purchase_payment", u"复核"),
+            ("ratify_purchase_payment", u"批准"),
+            ("pay_purchase_payment", u"付款"),
+            ("delete_purchase_payment", u"删除"),
+            ("export_purchase_payment", u"导出"),
+            ("print_purchase_payment", u"打印"),
+        )
+
+    @staticmethod
+    def getById(id):
+        instance = PurchasePayment.objects.filter(pk=id).first()
+        if not instance:
+            raise CustomError(u'未找到相应的付款单')
+        return instance
+
+    def updateAmount(self):
+        sum_amount = 0
+        sum_row = PurchasePaymentDetail.objects.filter(main=self).aggregate(sum_amount=Sum('purchase_detail__amount'))
+        if sum_row:
+            sum_amount = sum_row['sum_amount'] or 0
+
+        self.amount = sum_amount
+        self.save()
+
+    def updateActualAmount(self):
+        sum_actual_amount = 0
+        sum_row = PurchasePay.objects.filter(main=self).aggregate(sum_actual_amount=Sum('actual_amount'))
+        if sum_row:
+            sum_actual_amount = sum_row['sum_actual_amount'] or 0
+
+        self.actual_amount = sum_actual_amount
+        self.save()
+
+    def save(self, *args, **kwargs):
+        if self.no == None or self.no == '':
+            now = timezone.now()
+            rows = PurchasePayment.objects.filter(create_time__gte=now.strftime('%Y-%m-%d')).order_by('-no')
+            count = rows.count()
+            if count == 0:
+                self.no = 'CY%s%03d' % (now.strftime('%Y%m%d'), count + 1)
+            else:
+                self.no = rows[0].no[:2] + str(int(rows[0].no[2:]) + 1)
+        super(PurchasePayment, self).save(*args, **kwargs)
+
+
+class PurchasePaymentDetail(models.Model):
+    main = models.ForeignKey(PurchasePayment, verbose_name=u"付款单", on_delete=models.PROTECT)
+    purchase_detail = models.ForeignKey(PurchaseOrderDetail, verbose_name=u"合同明细", on_delete=models.PROTECT)
+    class Meta:
+        db_table = "purchase_payment_detail"
+        verbose_name = u"入库查询"
+        ordering = ('-id',)
+        default_permissions = ()
+        permissions = ( # 付款明细
+            ("view_consumable_godownentry_query", u"浏览"),
+            ("export_consumable_godownentry_query", u"导出"),
+            ("print_consumable_godownentry_query", u"打印"),
+        )
+
+class PurchasePay(models.Model):
+    main = models.ForeignKey(PurchasePayment, verbose_name=u"付款单", on_delete=models.PROTECT)
+    payment_type = models.ForeignKey(Option, verbose_name=u"付款方式", on_delete=models.PROTECT)
+    payment_time = models.DateTimeField(verbose_name=u"付款时间", default=timezone.now)
+    actual_amount = models.BigIntegerField(verbose_name=u'实付金额', default=0)
+    payment_user = models.ForeignKey(User, verbose_name=u"付款人", editable=False,on_delete=models.PROTECT)
+    payment_department = models.ForeignKey(Department, verbose_name=u"付款部门", editable=False, on_delete=models.PROTECT)
+    notes = models.CharField(max_length=200, verbose_name=u"备注", blank=True, null=True)
+
+    class Meta:
+        db_table = "purchase_pay"
+        verbose_name = u"付款明细"
+        ordering = ('-id',)
+        default_permissions = ()
+
+    @staticmethod
+    def getByMainId(id):
+        instances = PurchasePay.objects.filter(main_id=id)
+        if not instances:
+            raise CustomError(u'未找到相应的付款单')
+        return instances
+
+    @staticmethod
+    def getById(id):
+        instance = PurchasePay.objects.filter(id=id).first()
+        if not instance:
+            raise CustomError(u'未找到相应的付款单')
+        return instance
+
+class GodownEntry(models.Model):
+    MATERIAL = 0
+    CONSUMABLE = 1
+    PRODUCT_CHOICES = (
+        (MATERIAL, u'原料'),
+        (CONSUMABLE, u'耗材'),
+    )
+    PREFIX_CHOICES = (
+        (MATERIAL, 'MR'),
+        (CONSUMABLE, 'CR'),
+    )
+
+    no = models.CharField(max_length=20, verbose_name=u"入库单号", editable=False)
+    supplier = models.ForeignKey(Supplier, verbose_name=u'供应商', on_delete=models.PROTECT)
+    purchase_order = models.ForeignKey(PurchaseOrder, verbose_name=u'采购合同', blank=True, null=True, on_delete=models.PROTECT)
+    total_count = models.BigIntegerField(verbose_name=u'数量合计', default=0)
+    total_amount = models.BigIntegerField(verbose_name=u'金额合计', default=0)
+    total_invoice_amount = models.BigIntegerField(verbose_name=u'发票金额合计', default=0) #等于明细里面的发票金额合计
+    create_user = models.ForeignKey(User, related_name='godown_entry_ref_create_user', verbose_name=u"创建人", on_delete=models.PROTECT, editable=False)
+    department = models.ForeignKey(Department, verbose_name=u"创建部门", on_delete=models.PROTECT, editable=False)
+    create_time = models.DateTimeField(verbose_name=u"创建时间", default=timezone.now)
+    status = models.PositiveSmallIntegerField(choices=settings.CHECK_STATUS_CHOICES, default=settings.DEFAULT, verbose_name=u"审核状态")
+    check_user = models.ForeignKey(User, related_name='godown_entry_ref_check_user', verbose_name=u"审核人", on_delete=models.PROTECT, blank=True, null=True)
+    check_time = models.DateTimeField(verbose_name=u"审核时间", blank=True, null=True)
+    notes = models.CharField(max_length=200, verbose_name=u"备注", blank=True, null=True)
+    warehouse = models.ForeignKey(Warehouse, verbose_name=u'仓别', on_delete=models.PROTECT)
+    product_type = models.PositiveSmallIntegerField(choices=PRODUCT_CHOICES, verbose_name=u"产品类型")
+    total_return_count = models.BigIntegerField(verbose_name=u"退货数量合计", default=0)
+    total_return_amount = models.BigIntegerField(verbose_name=u"退货金额合计", default=0)
+    total_deliver_count = models.BigIntegerField(verbose_name=u"出库数量合计", default=0)
+    total_deliver_amount = models.BigIntegerField(verbose_name=u"出库金额合计", default=0)
+
+
+    class Meta:
+        db_table = "godown_entry"
+        verbose_name = u"入库管理"
+        ordering = ('-id',)
+        index_together = (
+            'create_time', 'check_time', 'status'
+        )
+        unique_together = (
+            'no',
+        )
+        default_permissions = ()
+        permissions = (
+            ("view_material_godown_entry", u"浏览"),
+            ("add_material_godown_entry", u"添加"),
+            ("check_material_godown_entry", u"审核"),
+            ("delete_material_godown_entry", u"删除"),
+            ("export_material_godown_entry", u"导出"),
+            ("import_material_godown_entry", u"导入"),
+            ("print_material_godown_entry", u"打印"),
+            ("edit_material_godown_entry", u"高级修改"),
+        )
+
+    @staticmethod
+    def getById(id):
+        instances = GodownEntry.objects.filter(id=id).first()
+        if not instances:
+            raise CustomError(u'未找到相应的入库单')
+        return instances
+
+    @staticmethod
+    def getPermissionByType(type, action):
+        permissions = GodownEntry.getPermissionMap()
+        type = GodownEntry.getValidType(type)
+        return permissions[type][action]
+
+    @staticmethod
+    def getPermissionMap():
+        permissions = {
+            GodownEntry.MATERIAL: {'view': 'purchase.view_material_godown_entry',
+                                   'add': 'purchase.add_material_godown_entry',
+                                   'check': 'purchase.check_material_godown_entry',
+                                   'delete': 'purchase.delete_material_godown_entry',
+                                   'export': 'purchase.export_material_godown_entry',
+                                   'import': 'purchase.import_material_godown_entry',
+                                   'print': 'purchase.print_material_godown_entry',
+                                   'edit': "purchase.edit_consumable_godown_entry",
+                                   },
+
+            GodownEntry.CONSUMABLE: {'view': 'purchase.view_consumable_godown_entry',
+                                   'add': 'purchase.add_consumable_godown_entry',
+                                   'check': 'purchase.check_consumable_godown_entry',
+                                   'delete': 'purchase.delete_consumable_godown_entry',
+                                   'export': 'purchase.export_consumable_godown_entry',
+                                   'import': 'purchase.import_consumable_godown_entry',
+                                   'print': 'purchase.print_consumable_godown_entry',
+                                   'edit': "purchase.edit_consumable_godown_entry",
+                                   },
+        }
+        return permissions
+
+
+    @staticmethod
+    def getValidType(type):
+        try:
+            type = int(type)
+        except:
+            raise CustomError(u'错误的入库类型')
+
+        types = (r[0] for r in GodownEntry.PRODUCT_CHOICES)
+        if type not in types:
+            raise CustomError(u'无效的入库类型')
+        return type
+
+    def getPermission(self, action):
+        permissions = GodownEntry.getPermissionMap()
+        return permissions[self.product_type][action]
+
+    def save(self, *args, **kwargs):
+        if self.no == None or self.no == '':
+            now = timezone.now()
+            rows = GodownEntry.objects.filter(create_time__gte=now.strftime('%Y-%m-%d'), product_type=self.product_type).order_by('-no')
+            count = rows.count()
+            prefix = GodownEntry.PREFIX_CHOICES[self.product_type][1]
+            if count == 0:
+                self.no = '%s%s%03d' % (prefix, now.strftime('%Y%m%d'), count + 1)
+            else:
+                self.no = rows[0].no[:2] + str(int(rows[0].no[2:]) + 1)
+        super(GodownEntry, self).save(*args, **kwargs)
+
+    def update_total(self):
+        total_count = 0
+        total_amount = 0
+        total_invoice_amount = 0
+        sum_row = GodownEntryDetail.objects.filter(main=self).aggregate(total_count=Sum('count'),\
+                                        total_amount=Sum('amount'), total_invoice_amount=Sum('invoice_amount'))
+        if sum_row:
+            total_count = sum_row['total_count'] or 0
+            total_amount = sum_row['total_amount'] or 0
+            total_invoice_amount = sum_row['total_invoice_amount'] or 0
+        self.total_count = total_count
+        self.total_amount = total_amount
+        self.total_invoice_amount = total_invoice_amount
+        self.save()
+
+    def update_redundant(self):
+        total_return_count = 0
+        total_return_amount = 0
+        total_deliver_count = 0
+        total_deliver_amount = 0
+
+        sum_row = GodownEntryDetail.objects.filter(main=self).aggregate(sum_return_count=Sum('return_count'), sum_return_amount=Sum('return_amount'),
+                                                                        sum_deliver_count=Sum('deliver_count'), sum_deliver_amount=Sum('deliver_amount'))
+        if sum_row:
+            total_return_count = sum_row['sum_return_count'] or 0
+            total_return_amount = sum_row['sum_return_amount'] or 0
+            total_deliver_count = sum_row['sum_deliver_count'] or 0
+            total_deliver_amount = sum_row['sum_deliver_amount'] or 0
+
+        self.total_return_count = total_return_count
+        self.total_return_amount = total_return_amount
+        self.total_deliver_count = total_deliver_count
+        self.total_deliver_amount = total_deliver_amount
+        self.save()
+
+
+class GodownEntryDetail(models.Model):
+    main = models.ForeignKey(GodownEntry, verbose_name=u'入库单',related_name='godown_entry_detail_ref_main', on_delete=models.PROTECT)
+    product_base = models.ForeignKey(ProductBase, verbose_name=u'产品', on_delete=models.PROTECT)
+    count = models.BigIntegerField(verbose_name=u'数量', default=0)
+    price = models.BigIntegerField(verbose_name=u'单价', default=0)
+    warehouse_stock = models.ForeignKey(WarehouseStock, verbose_name=u'仓别库存', on_delete=models.PROTECT, editable=False)
+    stock_record = models.ForeignKey(WarehouseStockRecord, verbose_name=u'库存记录', on_delete=models.PROTECT, null=True,blank=True, related_name='godown_entry_detail_ref_stock_record',)
+
+    amount = models.BigIntegerField(verbose_name=u'金额', default=0)#数量*单价
+    invoice_amount = models.BigIntegerField(verbose_name=u'发票金额', default=0) #手工录入
+    return_count = models.BigIntegerField(verbose_name=u"退货数量", default=0)
+    return_amount = models.BigIntegerField(verbose_name=u"退货金额", default=0)
+    deliver_count = models.BigIntegerField(verbose_name=u"出库数量", default=0)
+    deliver_amount = models.BigIntegerField(verbose_name=u"出库金额", default=0)
+    notes = models.CharField(max_length=200, verbose_name=u"备注", blank=True, null=True)
+
+    class Meta:
+        db_table = "godown_entry_detail"
+        verbose_name = u"入库管理"
+        ordering = ('-id',)
+        default_permissions = ()
+        permissions = (# 原料/耗材入库明细
+            ("view_consumable_godown_entry", u"浏览"),
+            ("add_consumable_godown_entry", u"添加"),
+            ("check_consumable_godown_entry", u"审核"),
+            ("delete_consumable_godown_entry", u"删除"),
+            ("export_consumable_godown_entry", u"导出"),
+            ("import_consumable_godown_entry", u"导入"),
+            ("print_consumable_godown_entry", u"打印"),
+            ("edit_consumable_godown_entry", u"高级修改"),
+        )
+
+    @staticmethod
+    def getById(id):
+        instances = GodownEntryDetail.objects.filter(id=id).first()
+        if not instances:
+            raise CustomError(u'未找到相应的入库明细')
+        return instances
+
+
+class GodownEntryReturn(models.Model):
+    MATERIAL = 0
+    CONSUMABLE = 1
+    PRODUCT_CHOICES = (
+        (MATERIAL, u'原料'),
+        (CONSUMABLE, u'耗材'),
+    )
+    PREFIX_CHOICES = (
+        (MATERIAL, 'MT'),
+        (CONSUMABLE, 'CT'),
+    )
+    no = models.CharField(max_length=20, verbose_name=u"单号", editable=False)
+    type = models.PositiveSmallIntegerField(choices=PRODUCT_CHOICES, verbose_name=u"产品类型")
+    supplier = models.ForeignKey(Supplier, verbose_name=u"供应商", null=True, blank=True, on_delete=models.PROTECT)
+    warehouse = models.ForeignKey(Warehouse, verbose_name=u"仓别", on_delete=models.PROTECT)
+
+    create_time = models.DateTimeField(verbose_name=u"创建时间", default=timezone.now)
+    create_user = models.ForeignKey(User, related_name='godown_entry_return_ref_create_user', verbose_name=u"创建人", on_delete=models.PROTECT, editable=False)
+    department = models.ForeignKey(Department, verbose_name=u"创建部门", editable=False, on_delete=models.PROTECT)
+    status = models.PositiveSmallIntegerField(choices=settings.CHECK_STATUS_CHOICES, default=settings.DEFAULT, verbose_name=u"审核状态")
+    check_user = models.ForeignKey(User, related_name='godown_entry_return_ref_check_user', verbose_name=u"审核人", on_delete=models.PROTECT, blank=True, null=True)
+    check_time = models.DateTimeField(verbose_name=u"审核时间", blank=True, null=True)
+    notes = models.CharField(max_length=200, verbose_name=u"备注", blank=True, null=True)
+
+    total_count = models.BigIntegerField(verbose_name=u"合计数量", default=0)
+    total_amount = models.BigIntegerField(verbose_name=u"合计金额", default=0)
+
+    class Meta:
+        db_table = "godown_entry_return"
+        verbose_name = u"退货管理"
+        ordering = ('-id',)
+        index_together = (
+            'create_time', 'check_time', 'status'
+        )
+        unique_together = (
+            'no',
+        )
+        default_permissions = ()
+        permissions = (
+            ("view_material_godown_entry_return", u"浏览"),
+            ("add_material_godown_entry_return", u"添加"),
+            ("check_material_godown_entry_return", u"审核"),
+            ("delete_material_godown_entry_return", u"删除"),
+            ("export_material_godown_entry_return", u"导出"),
+            ("print_material_godown_entry_return", u"打印"),
+        )
+
+    @staticmethod
+    def getById(id):
+        instances = GodownEntryReturn.objects.filter(id=id).first()
+        if not instances:
+            raise CustomError(u'未找到相应的退货单')
+        return instances
+
+    @staticmethod
+    def getPermissionByType(type, action):
+        permissions = GodownEntryReturn.getPermissionMap()
+        type = GodownEntryReturn.getValidType(type)
+        return permissions[type][action]
+
+    @staticmethod
+    def getPermissionMap():
+        permissions = {
+            GodownEntryReturn.MATERIAL: {'view': 'purchase.view_material_godown_entry_return',
+                                   'add': 'purchase.add_material_godown_entry_return',
+                                   'check': 'purchase.check_material_godown_entry_return',
+                                   'delete': 'purchase.delete_material_godown_entry_return',
+                                   'export': 'purchase.export_material_godown_entry_return',
+                                   'print': 'purchase.print_material_godown_entry_return'
+                                   },
+
+            GodownEntryReturn.CONSUMABLE: {'view': 'purchase.view_consumable_godown_entry_return',
+                                     'add': 'purchase.add_consumable_godown_entry_return',
+                                     'check': 'purchase.check_consumable_godown_entry_return',
+                                     'delete': 'purchase.delete_consumable_godown_entry_return',
+                                     'export': 'purchase.export_consumable_godown_entry_return',
+                                     'print': 'purchase.print_consumable_godown_entry_return'
+                                     },
+        }
+        return permissions
+
+    @staticmethod
+    def getValidType(type):
+        try:
+            type = int(type)
+        except:
+            raise CustomError(u'错误的退货类型')
+
+        types = (r[0] for r in GodownEntryReturn.PRODUCT_CHOICES)
+        if type not in types:
+            raise CustomError(u'无效的退货类型')
+        return type
+
+    def getPermission(self, action):
+        permissions = GodownEntryReturn.getPermissionMap()
+        return permissions[self.type][action]
+
+    def save(self, *args, **kwargs):
+        if self.no == None or self.no == '':
+            now = timezone.now()
+            rows = GodownEntryReturn.objects.filter(create_time__gte=now.strftime('%Y-%m-%d'), type=self.type).order_by('-no')
+            count = rows.count()
+            prefix = GodownEntryReturn.PREFIX_CHOICES[self.type][1]
+            if count == 0:
+                self.no = '%s%s%03d' % (prefix, now.strftime('%Y%m%d'), count + 1)
+            else:
+                self.no = rows[0].no[:2] + str(int(rows[0].no[2:]) + 1)
+        super(GodownEntryReturn, self).save(*args, **kwargs)
+
+
+    def update_total(self):
+        total_count = 0
+        total_amount = 0
+        sum_row = GodownEntryReturnDetail.objects.filter(main=self).aggregate(total_count=Sum('count'), total_amount=Sum('amount'))
+        if sum_row:
+            total_count = sum_row['total_count'] or 0
+            total_amount = sum_row['total_amount'] or 0
+        self.total_count = total_count
+        self.total_amount = total_amount
+        self.save()
+
+
+class GodownEntryReturnDetail(models.Model):
+    main = models.ForeignKey(GodownEntryReturn, verbose_name=u"退货单", related_name='godown_entry_return_detail_ref_main', on_delete=models.PROTECT)
+    product_base = models.ForeignKey(ProductBase, verbose_name=u'产品')
+    count = models.BigIntegerField(verbose_name=u"数量", default=0)
+    price = models.BigIntegerField(verbose_name=u"单价", default=0)
+    amount = models.BigIntegerField(verbose_name=u"金额", default=0)
+    warehouse_stock = models.ForeignKey(WarehouseStock, verbose_name=u'仓别库存', on_delete=models.PROTECT, editable=False)
+    warehouse_record = models.ForeignKey(WarehouseRecord, verbose_name=u'出入库记录', on_delete=models.PROTECT, null=True,
+                                     blank=True, related_name='godown_entry_return_detail_ref_warehouse_record', )
+    godownentry_detail = models.ForeignKey(GodownEntryDetail, verbose_name=u"入库明细", on_delete=models.PROTECT, null=True, blank=True)
+    notes = models.CharField(max_length=200, verbose_name=u"备注", blank=True, null=True)
+
+    class Meta:
+        db_table = "godown_entry_detail_return"
+        verbose_name = u"退货管理"
+        ordering = ('-id',)
+        default_permissions = ()
+        permissions = (# 退货明细
+            ("view_consumable_godown_entry_return", u"浏览"),
+            ("add_consumable_godown_entry_return", u"添加"),
+            ("check_consumable_godown_entry_return", u"审核"),
+            ("delete_consumable_godown_entry_return", u"删除"),
+            ("export_consumable_godown_entry_return", u"导出"),
+            ("print_consumable_godown_entry_return", u"打印"),
+        )
+
+    @staticmethod
+    def getById(id):
+        instances = GodownEntryReturnDetail.objects.filter(id=id).first()
+        if not instances:
+            raise CustomError(u'未找到相应的退货明细')
+        return instances
+
+    @staticmethod
+    def getById2(id):
+        instances = GodownEntryReturnDetail.objects.filter(id=id).first()
+        if not instances:
+            return None
+        return instances

+ 419 - 0
apps/purchase/resources.py

@@ -0,0 +1,419 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+from import_export import resources
+from import_export.fields import Field
+from apps.base import ExcelImporter
+from apps.base import Formater
+
+class PurchasePlanResource(resources.Resource):
+
+    def __init__(self):
+        super(PurchasePlanResource, self).__init__()
+        self.fields['no'] = Field(attribute='no')
+        self.fields['name'] = Field(attribute='name')
+        self.fields['total_count'] = Field(attribute='total_count')
+        self.fields['create_time'] = Field(attribute='create_time')
+        self.fields['create_user_text'] = Field(attribute='create_user_text')
+        self.fields['demend_user_text'] = Field(attribute='demend_user_text')
+        self.fields['status_text'] = Field(attribute='status_text')
+        self.fields['check_user_text'] = Field(attribute='check_user_text')
+        self.fields['check_time'] = Field(attribute='check_time')
+        self.fields['notes'] = Field(attribute='notes')
+        self.fields['purchase_users'] = Field(attribute='purchase_users')
+
+    def get_export_headers(self):
+        return [u'单号', u'名称', u'合计数量', u'需求人', u'创建时间', u'创建人', u'审核状态', u'审核时间',u'审核人', u'采购员', u'备注']
+
+    class Meta:
+        fields = ('no', 'name','total_count', 'demend_user_text', 'create_time', 'create_user_text', 'status_text','check_time', 'check_user_text',
+                   'purchase_users','notes')
+        export_order = fields
+
+
+class PurchasePlanDetailResource(resources.Resource):
+
+    def __init__(self):
+        super(PurchasePlanDetailResource, self).__init__()
+        self.fields['name'] = Field(attribute='name')
+        self.fields['model'] = Field(attribute='model')
+        self.fields['quality_request_text'] = Field(attribute='quality_request_text')
+        self.fields['purchase_count'] = Field(attribute='purchase_count')
+        self.fields['product_time'] = Field(attribute='product_time')
+        self.fields['stock_count'] = Field(attribute='stock_count')
+        self.fields['notes'] = Field(attribute='notes')
+
+    def get_export_headers(self):
+        return [u'产品名称', u'产品代码', u'质量要求', u'数量', u'需求时间', u'当前库存', u'备注']
+
+    class Meta:
+        fields = ('name', 'model', 'quality_request_text', 'purchase_count', 'product_time', 'stock_count',
+                  'notes',)
+        export_order = fields
+
+class PurchasePriceResource(resources.Resource):
+
+    def __init__(self):
+        super(PurchasePriceResource, self).__init__()
+        self.fields['purchase_user_text'] = Field(attribute='purchase_user_text')
+        self.fields['name'] = Field(attribute='name')
+        self.fields['model'] = Field(attribute='model')
+        self.fields['quality_request_text'] = Field(attribute='quality_request_text')
+        self.fields['purchase_count'] = Field(attribute='purchase_count')
+        self.fields['supplier_text'] = Field(attribute='supplier_text')
+        self.fields['price'] = Field(attribute='price')
+        self.fields['notes'] = Field(attribute='notes')
+
+    def get_export_headers(self):
+        return [u'员工', u'产品', u'代码', u'质量要求', u'数量', u'供应商', u'价格', u'备注']
+
+    class Meta:
+        fields = ('purchase_user_text', 'name', 'model', 'quality_request_text', 'purchase_count', 'supplier_text', 'price',
+                  'notes',)
+        export_order = fields
+
+class PurchasePlanImporter(ExcelImporter):
+    fields = {
+        u'产品类别': (True, ExcelImporter.formatUnicode),
+        u'产品代码': (True, ExcelImporter.formatUnicode),
+        u'数量': (True, ExcelImporter.formatFloatGtZ),
+        u'质量要求': (False, ExcelImporter.formatUnicode),
+        u'需求时间': (False, ExcelImporter.formatDateTime),
+        u'备注': (False, ExcelImporter.formatUnicode),
+    }
+
+class PurchaseOrderResource(resources.Resource):
+    def __init__(self):
+        super(PurchaseOrderResource, self).__init__()
+        self.fields['order_no'] = Field(attribute='order_no')
+        self.fields['no'] = Field(attribute='no')
+        self.fields['supplier_name'] = Field(attribute='supplier_name')
+        self.fields['products'] = Field(attribute='products')
+        self.fields['payment_type'] = Field(attribute='payment_type')
+        self.fields['count'] = Field(attribute='count')
+        self.fields['amount'] = Field(attribute='amount')
+        self.fields['apply_amount'] = Field(attribute='apply_amount')
+        self.fields['status_text'] = Field(attribute='status_text')
+        self.fields['arrval_text'] = Field(attribute='arrval_text')
+        self.fields['entries'] = Field(attribute='entries')
+        self.fields['create_user_text'] = Field(attribute='create_user_text')
+        self.fields['create_time'] = Field(attribute='create_time')
+        self.fields['check_user_text'] = Field(attribute='check_user_text')
+        self.fields['check_time'] = Field(attribute='check_time')
+        self.fields['notes'] = Field(attribute='notes')
+
+    def get_export_headers(self):
+        return [u'单号', u'合同号', u'供应商', u'产品', u'付款方式', u'合计数量', u'合计金额',u'已申请金额', u'状态', u'到货状态', u'批次',
+                 u'创建时间', u'创建人', u'审核时间', u'审核人',  u'备注',]
+
+    class Meta:
+        fields = ('order_no', 'no', 'supplier_name', 'products', 'payment_type', 'count', 'amount', 'apply_amount', 'status_text', 'arrval_text',
+                  'entries', 'create_time', 'create_user_text', 'check_time', 'check_user_text', 'notes')
+        export_order = fields
+
+class PurchaseOrderDetailResource(resources.Resource):
+    def __init__(self):
+        super(PurchaseOrderDetailResource, self).__init__()
+        self.fields['name'] = Field(attribute='name')
+        self.fields['model'] = Field(attribute='model')
+        self.fields['quality_request_text'] = Field(attribute='quality_request_text')
+        self.fields['count'] = Field(attribute='count')
+        self.fields['price'] = Field(attribute='price')
+        self.fields['amount'] = Field(attribute='amount')
+        self.fields['arrval_count'] = Field(attribute='arrval_count')
+        self.fields['invoice_no'] = Field(attribute='invoice_no')
+        self.fields['invoice_amount'] = Field(attribute='invoice_amount')
+
+    def get_export_headers(self):
+        return [u'产品名称', u'产品代码', u'质量标准',u'数量', u'单价', u'金额',
+                u'到货数量', u'发票', u'付款金额',]
+
+    class Meta:
+        fields = ('name', 'model', 'quality_request_text', 'count', 'price', 'amount','arrval_count', 'invoice_no', 'invoice_amount',)
+        export_order = fields
+
+class PurchaseInvoiceResource(resources.Resource):
+    def __init__(self):
+        super(PurchaseInvoiceResource, self).__init__()
+        self.fields['order_no'] = Field(attribute='order_no')
+        self.fields['name'] = Field(attribute='name')
+        self.fields['model'] = Field(attribute='model')
+        self.fields['supplier_name'] = Field(attribute='supplier_name')
+        self.fields['amount'] = Field(attribute='amount')
+        self.fields['invoice_no'] = Field(attribute='invoice_no')
+        self.fields['invoice_amount'] = Field(attribute='invoice_amount')
+        self.fields['invoice_date'] = Field(attribute='invoice_date')
+        self.fields['create_time'] = Field(attribute='create_time')
+        self.fields['create_user_text'] = Field(attribute='create_user_text')
+        self.fields['check_status_text'] = Field(attribute='check_status_text')
+        self.fields['check_user_text'] = Field(attribute='check_user_text')
+        self.fields['check_time'] = Field(attribute='check_time')
+
+    def get_export_headers(self):
+        return [u'合同号', u'产品名称', u'产品代码',u'供应商', u'金额', u'发票号',
+                u'发票金额',u'开票日期', u'创建时间', u'创建人',u'审核状态', u'审核时间', u'审核人']
+
+    class Meta:
+
+        fields = ('order_no', 'name', 'model', 'supplier_name',
+                  'amount', 'invoice_no', 'invoice_amount','invoice_date', 'create_time', 'create_user_text',
+                  'check_status_text', 'check_time', 'check_user_text')
+        export_order = fields
+
+
+class GodownEntryResource(resources.Resource):
+
+    def __init__(self, is_show_cost=True):
+        super(GodownEntryResource, self).__init__()
+        self.is_show_cost = is_show_cost
+        self.fields['no'] = Field(attribute='no')
+        self.fields['supplier_text'] = Field(attribute='supplier_text')
+        self.fields['total_count'] = Field(attribute='total_count')
+        self.fields['warehouse_text'] = Field(attribute='warehouse_text')
+        self.fields['purchase_order_no'] = Field(attribute='purchase_order_no')
+        self.fields['create_user_text'] = Field(attribute='create_user_text')
+        self.fields['create_time'] = Field(attribute='create_time')
+        self.fields['status_text'] = Field(attribute='status_text')
+        self.fields['check_user_text'] = Field(attribute='check_user_text')
+        self.fields['check_time'] = Field(attribute='check_time')
+        self.fields['notes'] = Field(attribute='notes')
+        if is_show_cost:
+            self.fields['total_amount'] = Field(attribute='total_amount')
+            self.fields['total_invoice_amount'] = Field(attribute='total_invoice_amount')
+            fields = ('no', 'supplier_text', 'total_count', 'total_amount', 'total_invoice_amount', 'warehouse_text', 'purchase_order_no', 'create_time', 'create_user_text', 'status_text', 'check_time',
+                'check_user_text', 'notes')
+        else:
+            fields = (
+            'no', 'supplier_text', 'total_count', 'warehouse_text', 'purchase_order_no', 'create_time', 'create_user_text', 'status_text', 'check_time',
+            'check_user_text', 'notes')
+        self._meta.export_order = fields
+
+    def get_export_headers(self):
+        if self.is_show_cost:
+            return [u'入库单号', u'供应商', u'合计数量', u'合计金额', u'合计发票金额', u'仓别',u'合同号', u'创建时间', u'创建人' , u'审核状态', u'审核时间',u'审核人', u'备注']
+        return [u'入库单号', u'供应商', u'合计数量', u'仓别',u'合同号', u'创建时间', u'创建人', u'审核状态', u'审核时间', u'审核人', u'备注']
+
+
+
+class GodownEntryDetailResource(resources.Resource):
+
+    def __init__(self, is_show_cost=True):
+        super(GodownEntryDetailResource, self).__init__()
+        self.is_show_cost = is_show_cost
+        self.fields['product_base_text'] = Field(attribute='product_base_text')
+        self.fields['product_base_model'] = Field(attribute='product_base_model')
+        self.fields['count'] = Field(attribute='count')
+        self.fields['unit_text'] = Field(attribute='unit_text')
+        self.fields['notes'] = Field(attribute='notes')
+        if is_show_cost:
+            self.fields['price'] = Field(attribute='price')
+            self.fields['amount'] = Field(attribute='amount')
+            self.fields['invoice_amount'] = Field(attribute='invoice_amount')
+            fields = ('product_base_text', 'product_base_model', 'count', 'price', 'amount','invoice_amount', 'unit_text', 'notes')
+        else:
+            fields = ('product_base_text', 'product_base_model', 'count', 'unit_text', 'notes')
+        self._meta.export_order = fields
+
+    def get_export_headers(self):
+        if self.is_show_cost:
+            return [u'原料名称', u'原料代码', u'数量', u'单价', u'金额',u'发票金额', u'计量单位', u'备注']
+        return [u'原料名称', u'原料代码', u'数量', u'计量单位', u'备注']
+
+
+class GodownEntryReturnResource(resources.Resource):
+
+    def __init__(self, is_show_cost=True):
+        super(GodownEntryReturnResource, self).__init__()
+        self.is_show_cost = is_show_cost
+        self.fields['no'] = Field(attribute='no')
+        self.fields['supplier_text'] = Field(attribute='supplier_text')
+        self.fields['total_count'] = Field(attribute='total_count')
+        self.fields['create_user_text'] = Field(attribute='create_user_text')
+        self.fields['create_time'] = Field(attribute='create_time')
+        self.fields['status_text'] = Field(attribute='status_text')
+        self.fields['check_user_text'] = Field(attribute='check_user_text')
+        self.fields['check_time'] = Field(attribute='check_time')
+        self.fields['notes'] = Field(attribute='notes')
+        if is_show_cost:
+            self.fields['total_amount'] = Field(attribute='total_amount')
+            fields = (
+            'no', 'supplier_text', 'total_count', 'total_amount', 'create_user_text', 'create_time', 'status_text',
+            'check_user_text',
+            'check_time', 'notes',)
+        else:
+            fields = (
+            'no', 'supplier_text', 'total_count', 'create_user_text', 'create_time', 'status_text',
+            'check_user_text',
+            'check_time', 'notes',)
+        self._meta.export_order = fields
+
+
+    def get_export_headers(self):
+        if self.is_show_cost:
+            return [u'退货单号', u'供应商', u'合计数量', u'合计金额', u'创建人', u'创建时间' , u'审核状态',u'审核人', u'审核时间', u'备注']
+        return [u'退货单号', u'供应商', u'合计数量', u'创建人', u'创建时间' , u'审核状态',u'审核人', u'审核时间', u'备注']
+
+
+class GodownEntryReturnDetailResource(resources.Resource):
+
+    def __init__(self, is_show_cost=True):
+        super(GodownEntryReturnDetailResource, self).__init__()
+        self.is_show_cost = is_show_cost
+        self.fields['product_base_text'] = Field(attribute='product_base_text')
+        self.fields['product_base_model'] = Field(attribute='product_base_model')
+        self.fields['godownentry_no'] = Field(attribute='godownentry_no')
+        self.fields['count'] = Field(attribute='count')
+        self.fields['notes'] = Field(attribute='notes')
+        if is_show_cost:
+            self.fields['price'] = Field(attribute='price')
+            self.fields['amount'] = Field(attribute='amount')
+            fields = ('product_base_text', 'product_base_model', 'godownentry_no', 'count', 'price', 'amount', 'notes')
+        else:
+            fields = ('product_base_text', 'product_base_model', 'godownentry_no', 'count', 'notes')
+        self._meta.export_order = fields
+
+    def get_export_headers(self):
+        if self.is_show_cost:
+            return [u'原料名称', u'原料代码', u'入库单号', u'数量', u'单价', u'金额', u'备注']
+        return [u'原料名称', u'原料代码', u'入库单号', u'数量', u'备注']
+
+
+class PurchasePaymentResource(resources.Resource):
+    def __init__(self):
+        super(PurchasePaymentResource, self).__init__()
+        self.fields['no'] = Field(attribute='no')
+        self.fields['order_no'] = Field(attribute='order_no')
+        self.fields['supplier_name'] = Field(attribute='supplier_name')
+        self.fields['supplier_account'] = Field(attribute='supplier_account')
+        self.fields['amount'] = Field(attribute='amount')
+        self.fields['create_time'] = Field(attribute='create_time')
+        self.fields['create_user_text'] = Field(attribute='create_user_text')
+        self.fields['status_text'] = Field(attribute='status_text')
+        self.fields['check_user_text'] = Field(attribute='check_user_text')
+        self.fields['check_time'] = Field(attribute='check_time')
+        self.fields['actual_amount'] = Field(attribute='actual_amount')
+        self.fields['apply_amount'] = Field(attribute='apply_amount')
+        self.fields['notes'] = Field(attribute='notes')
+
+    def get_export_headers(self):
+        return [u'付款单号', u'合同号',u'供应商',u'供应商账号', u'合同总金额', u'申请金额',u'实收金额', u'创建时间', u'创建人', u'审核状态', u'审核时间',
+                u'审核人',  u'备注']
+
+    class Meta:
+        fields = ('no','order_no', 'supplier_name', 'supplier_account', 'amount', 'apply_amount','actual_amount', 'create_time', 'create_user_text', 'status_text',
+                   'check_time','check_user_text', 'notes')
+        export_order = fields
+
+
+class PurchasePaymentDetailResource(resources.Resource):
+
+    def __init__(self):
+        super(PurchasePaymentDetailResource, self).__init__()
+        self.fields['product__name'] = Field(attribute='product__name')
+        self.fields['product__model'] = Field(attribute='product__model')
+        self.fields['main__no'] = Field(attribute='main__no')
+        self.fields['amount'] = Field(attribute='amount')
+
+    def dehydrate_amount(self, instance):
+        return Formater.formatAmountShowWithTwoDecimalPlaces(instance.amount)
+
+    def get_export_headers(self):
+        return [u'产品名称', u'产品代码', u'合同号', u'金额',]
+
+    class Meta:
+        fields = ('product__name', 'product__model', 'main__no', 'amount',)
+        export_order = fields
+
+class GodownEntryQueryResource(resources.Resource):
+
+    def __init__(self, is_show_cost=True):
+        super(GodownEntryQueryResource, self).__init__()
+        self.is_show_cost = is_show_cost
+        self.fields['no'] = Field(attribute='no')
+        self.fields['type'] = Field(attribute='type')
+        self.fields['supplier'] = Field(attribute='supplier')
+        self.fields['product_name'] = Field(attribute='product_name')
+        self.fields['product_model'] = Field(attribute='product_model')
+        self.fields['product_unit'] = Field(attribute='product_unit')
+        self.fields['product_type'] = Field(attribute='product_type')
+        self.fields['warehouse'] = Field(attribute='warehouse')
+        self.fields['count'] = Field(attribute='count')
+        self.fields['return_count'] = Field(attribute='return_count')
+        self.fields['deliver_count'] = Field(attribute='deliver_count')
+        self.fields['surplus_count'] = Field(attribute='surplus_count')
+        self.fields['create_user'] = Field(attribute='create_user')
+        self.fields['check_time'] = Field(attribute='check_time')
+        self.fields['warehouse_place'] = Field(attribute='warehouse_place')
+        self.fields['notes'] = Field(attribute='notes')
+        if is_show_cost:
+            self.fields['price'] = Field(attribute='price')
+            self.fields['amount'] = Field(attribute='amount')
+            fields = (
+                'no', 'type', 'supplier', 'product_name', 'product_model', 'product_unit', 'product_type', 'warehouse',
+               'price', 'count', 'return_count', 'amount', 'deliver_count', 'surplus_count', 'create_user',
+                'check_time', 'warehouse_place', 'notes')
+        else:
+            fields = (
+                'no', 'type', 'supplier', 'product_name', 'product_model', 'product_unit', 'product_type', 'warehouse',
+                'count', 'return_count', 'deliver_count', 'surplus_count', 'create_user', 'check_time',
+                'warehouse_place', 'notes')
+        self._meta.export_order = fields
+
+    def get_export_headers(self):
+        if self.is_show_cost:
+            return [u'入库单号', u'入库类别', u'供应商', u'产品', u'代码', u'单位', u'产品类别', u'仓别', u'入库单价', u'数量'
+                , u'退货数量', u'合计金额', u'出库数量', u'剩余数量', u'创建人', u'审核时间', u'存放库位', u'备注']
+        return [u'入库单号', u'入库类别', u'供应商', u'产品', u'代码', u'单位', u'产品类别', u'仓别', u'数量'
+            , u'退货数量', u'出库数量', u'剩余数量', u'创建人', u'审核时间', u'存放库位', u'备注']
+
+
+class GodownEntryReturnQueryResource(resources.Resource):
+
+    def __init__(self, is_show_cost=True):
+        super(GodownEntryReturnQueryResource, self).__init__()
+        self.is_show_cost = is_show_cost
+        self.fields['main_no'] = Field(attribute='main_no')
+        self.fields['godownentry_no'] = Field(attribute='godownentry_no')
+        self.fields['main_supplier'] = Field(attribute='main_supplier')
+        self.fields['product_base_text'] = Field(attribute='product_base_text')
+        self.fields['product_base_model'] = Field(attribute='product_base_model')
+        self.fields['main_warehouse'] = Field(attribute='main_warehouse')
+        self.fields['count'] = Field(attribute='count')
+        self.fields['main_create_user'] = Field(attribute='main_create_user')
+        self.fields['main_create_time'] = Field(attribute='main_create_time')
+        self.fields['main_check_user'] = Field(attribute='main_check_user')
+        self.fields['main_check_time'] = Field(attribute='main_check_time')
+        self.fields['main_notes'] = Field(attribute='main_notes')
+        self.fields['notes'] = Field(attribute='notes')
+        if is_show_cost:
+            self.fields['price'] = Field(attribute='price')
+            self.fields['amount'] = Field(attribute='amount')
+            fields = (
+            'main_no', 'godownentry_no', 'main_supplier', 'product_base_text', 'product_base_model', 'main_warehouse',
+            'price',
+            'count','amount', 'main_create_user', 'main_create_time', 'main_check_user', 'main_check_time',
+            'main_notes', 'notes')
+        else:
+            fields = (
+            'main_no', 'godownentry_no', 'main_supplier', 'product_base_text', 'product_base_model', 'main_warehouse',
+            'count', 'main_create_user', 'main_create_time', 'main_check_user', 'main_check_time',
+            'main_notes', 'notes')
+        self._meta.export_order = fields
+
+
+    def get_export_headers(self):
+        if self.is_show_cost:
+            return [u'退货单号', u'入库单号',u'供应商', u'产品', u'代码', u'仓别', u'单价', u'数量',
+                    u'合计金额', u'创建人', u'创建时间', u'审核人员', u'审核时间', u'退货单备注', u'产品备注']
+        return [u'退货单号', u'入库单号',u'供应商', u'产品', u'代码', u'仓别', u'数量',
+                    u'创建人', u'创建时间', u'审核人员', u'审核时间', u'退货单备注', u'产品备注']
+
+
+class GodownEntryImporter(ExcelImporter):
+    fields = {
+        u'产品名称': (False, ExcelImporter.formatUnicode),
+        u'产品代码': (True, ExcelImporter.formatUnicode),
+        u'数量': (True, ExcelImporter.formatFloatGtZ),
+        u'单价': (True, ExcelImporter.formatFloatGeZ),
+        u'发票金额': (True, ExcelImporter.formatFloatGeZ),
+        u'备注': (False, ExcelImporter.formatUnicode),
+    }

+ 744 - 0
apps/purchase/serializers.py

@@ -0,0 +1,744 @@
+#coding=utf-8
+from libs import utils
+
+from django.db.models import Q, Sum
+from django.conf import settings
+from rest_framework import serializers
+from apps.base import Formater
+from apps.exceptions import CustomError
+from apps.foundation.models import BizLog
+from apps.serializer_errors import dump_serializer_errors
+from models import PurchasePlan, PurchaseUser, PurchasePlanDetail, PurchasePrice, PurchaseOrder, PurchaseOrderDetail, \
+    GodownEntry, PurchaseInvoiceImage, \
+    PurchasePayment, PurchasePaymentDetail, GodownEntry, GodownEntryDetail, GodownEntryReturn, GodownEntryReturnDetail, \
+    PurchasePay
+from libs.booleancharfield import BooleanCharField, PriceShowCharField, AmountShowCharField, CountShowCharField, \
+    TimeCharField, AmountShowCharFieldWithTwoDecimalPlaces
+
+from apps.warehouse.models import WarehouseStock
+from apps import base
+
+class PurchasePlanSerializer(serializers.ModelSerializer):
+    status = serializers.CharField(read_only=True)
+    total_count = CountShowCharField(read_only=True)
+    status_text = serializers.CharField(source='get_status_display', read_only=True)
+    demend_user_text = serializers.CharField(source='demend_user.name', read_only=True)
+    demend_department_text = serializers.CharField(source='demend_department.name', read_only=True)
+    create_time = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    check_user_text = serializers.CharField(source='check_user.name', read_only=True)
+    create_user_text = serializers.CharField(source='create_user.name', read_only=True)
+    purchase_users = serializers.SerializerMethodField()
+    check_time = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    is_compact_text = serializers.SerializerMethodField()
+    report_text = serializers.SerializerMethodField()
+
+    # check_user_text2 = serializers.CharField(source='check_user2.name', read_only=True)
+    # check_time2 = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    # check_user_text3 = serializers.CharField(source='check_user3.name', read_only=True)
+    # check_time3 = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    class Meta:
+        model = PurchasePlan
+        fields = '__all__'
+
+    def get_purchase_users(self, obj):
+        data = []
+        rows = PurchaseUser.objects.filter(purchase=obj)
+        data.extend([s[0] for s in rows.values_list('purchase_user__name')])
+        return ','.join(data)
+
+    def get_is_compact_text(self, obj):
+        data = obj.isCompart()
+        return data
+
+    def get_report_text(self, obj):
+        data = obj.isReport()
+        return data
+
+    @staticmethod
+    def factory(user, data, id=None):
+        if id:
+            instance = PurchasePlan.getById(id)
+        else:
+            instance = None
+        serializer = PurchasePlanSerializer(instance, data=data)
+        serializer.user = user
+        return serializer
+
+    def validate(self, data):
+        data['demend_department'] = None
+        if 'demend_user' in data and data['demend_user']:
+            data['demend_department'] = data['demend_user'].department
+        return data
+
+    def create(self, validated_data):
+        validated_data['create_user'] = self.user
+        validated_data['department'] = self.user.department
+        production = PurchasePlan.objects.create(**validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加采购计划[%s],id=%d" % (production.name, production.id),
+            validated_data
+        )
+        return production
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+    def update(self, instance, validated_data):
+        instance = super(PurchasePlanSerializer, self).update(instance, validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.UPDATE,
+            u"修改采购计划[%s],id=%d" % (instance.name, instance.id),
+            validated_data
+        )
+        return instance
+
+
+class PurchasePlanDetailSerializer(serializers.ModelSerializer):
+    name = serializers.CharField(source='product.name', read_only=True)
+    model = serializers.CharField(source='product.model', read_only=True)
+    stock_count = CountShowCharField(source='product.stock_count', read_only=True)
+    purchase_count = CountShowCharField()
+
+    class Meta:
+        model = PurchasePlanDetail
+        fields = '__all__'
+
+    @staticmethod
+    def factory(user, data, id=None):
+        if id:
+            instances = PurchasePlanDetail.getById(id)
+        else:
+            instances = None
+        serializer = PurchasePlanDetailSerializer(instances, data=data)
+        serializer.user = user
+        return serializer
+
+    def create(self, validated_data):
+        production = PurchasePlanDetail.objects.create(**validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加采购计划明细[%s],id=%d" % (production.product.name, production.id),
+            validated_data
+        )
+        return production
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+
+    def validate(self, data):
+        data['purchase_count'] = Formater.formatCount(data['purchase_count'])
+        return data
+
+    def update(self, instance, validated_data):
+        instance = super(PurchasePlanDetailSerializer, self).update(instance, validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.UPDATE,
+            u"修改采购计划明细[%s],id=%d" % (instance.name, instance.id),
+            validated_data
+        )
+        return instance
+
+
+class PurchaseUserSerializer(serializers.ModelSerializer):
+    purchase_user_text = serializers.CharField(source='purchase_user.name', read_only=True)
+
+    class Meta:
+        model = PurchaseUser
+        fields = '__all__'
+
+    @staticmethod
+    def factory(user, data):
+        instances = None
+        serializer = PurchaseUserSerializer(instances, data=data)
+        serializer.user = user
+        return serializer
+
+    def create(self, validated_data):
+        purchaseuser = PurchaseUser.objects.create(**validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加采购计划[%s]采购员[%s],id=%d" % (purchaseuser.purchase.no, purchaseuser.purchase_user.name, purchaseuser.id),
+            validated_data
+        )
+        return purchaseuser
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+    def update(self, instance, validated_data):
+        instance = super(PurchaseUserSerializer, self).update(instance, validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.UPDATE,
+            u"修改采购计划[%s]采购员[%s],id=%d" % (instance.purchase.no, instance.purchase_user.name, instance.id),
+            validated_data
+        )
+        return instance
+
+
+class PurchasePriceSerializer(serializers.ModelSerializer):
+    name = serializers.CharField(source='purchase_detail.product.name', read_only=True)
+    model = serializers.CharField(source='purchase_detail.product.model', read_only=True)
+    type_id = serializers.CharField(source='purchase_detail.product.type', read_only=True)
+    type_text = serializers.CharField(source='purchase_detail.product.get_type_display', read_only=True)
+    quality_request_text = serializers.CharField(source='purchase_detail.quality_request_text', read_only=True)
+    purchase_count = CountShowCharField(source='purchase_detail.purchase_count', read_only=True)
+    unit = serializers.CharField(source='purchase_detail.product.unit', read_only=True)
+    demend_user_text = serializers.CharField(source='purchase_user.purchase.demend_user.name', read_only=True)
+    purchase_user_text = serializers.CharField(source='purchase_user.name', read_only=True)
+    product_time = serializers.DateTimeField(format=base.DATE_FORMAT, source='purchase_detail.product_time', read_only=True)
+    supplier_text = serializers.CharField(source='supplier.name', read_only=True)
+    price = PriceShowCharField()
+    report_text = BooleanCharField(source='report', read_only=True)
+
+    class Meta:
+        model = PurchasePrice
+        fields = '__all__'
+
+    @staticmethod
+    def factory(user, data, id=None):
+        if id:
+            instances = PurchasePrice.getById(id)
+        else:
+            instances = None
+        serializer = PurchasePriceSerializer(instances, data=data)
+        serializer.user = user
+        return serializer
+
+    def create(self, validated_data):
+        purchaseprice = PurchasePrice.objects.create(**validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加采购计划[%s]报价[%s],id=%d" % (purchaseprice.purchase_detail.purchase.no, purchaseprice.price, purchaseprice.id),
+            validated_data
+        )
+        return purchaseprice
+
+    def validate(self, data):
+        if 'price' in data:
+            data['price'] = Formater.formatPrice(data['price'])
+        return data
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+    def update(self, instance, validated_data):
+        instance = super(PurchasePriceSerializer, self).update(instance, validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.UPDATE,
+            u"修改采购计划[%s]报价[%s],id=%d" % (instance.purchase_detail.purchase.no, instance.price, instance.id),
+            validated_data
+        )
+        return instance
+
+
+class PurchaseOrderSerializer(serializers.ModelSerializer):
+    create_time = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    check_time = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    status_text = serializers.CharField(source='get_status_display', read_only=True)
+    arrval_text = serializers.CharField(source='get_arrval_display', read_only=True)
+    check_user_text = serializers.CharField(source='check_user.name', read_only=True)
+    create_user_text = serializers.CharField(source='create_user.name', read_only=True)
+    supplier_name = serializers.CharField(source='supplier.name', read_only=True)
+    supplier_type = serializers.CharField(source='supplier.type', read_only=True)
+    supplier_contacts = serializers.CharField(source='supplier.contacts', read_only=True)
+    supplier_phone_number = serializers.CharField(source='supplier.phone_number', read_only=True)
+    supplier_opening_bank = serializers.CharField(source='supplier.opening_bank', read_only=True)
+    supplier_account = serializers.CharField(source='supplier.account', read_only=True)
+    products = serializers.SerializerMethodField()
+    entries = serializers.SerializerMethodField()
+    arrvals = serializers.SerializerMethodField()
+    is_pay = serializers.SerializerMethodField()
+    payment_amount = serializers.SerializerMethodField()
+    count = CountShowCharField()
+    amount = AmountShowCharFieldWithTwoDecimalPlaces()
+    apply_amount = AmountShowCharFieldWithTwoDecimalPlaces(read_only=True)
+    check_user_text2 = serializers.CharField(source='check_user2.name', read_only=True)
+    check_time2 = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    check_user_text3 = serializers.CharField(source='check_user3.name', read_only=True)
+    check_time3 = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+
+    class Meta:
+        model = PurchaseOrder
+        fields = '__all__'
+
+    def get_products(self, obj):
+        data = []
+        rows = PurchaseOrderDetail.objects.filter(main=obj)
+        data.extend([s[0] for s in rows.values_list('product__name')])
+        return ','.join(data)
+
+    def get_entries(self, obj):
+        data = []
+        rows = GodownEntry.objects.filter(purchase_order=obj)
+        data.extend([s[0] for s in rows.values_list('no')])
+        return ','.join(data)
+    def get_is_pay(self, obj):
+        data = 0
+        rows = PurchasePayment.objects.filter(order=obj)
+        if rows:
+            data = 1
+        return data
+
+    def get_arrvals(self, obj):
+        sum_rows = PurchaseOrderDetail.objects.filter(main=obj).aggregate(sum_count=Sum('arrval_count'))
+        return Formater.formatCountShow(sum_rows['sum_count'] or 0)
+
+    def get_payment_amount(self, obj):
+        sum_rows = PurchasePayment.objects.filter(order=obj).aggregate(sum_amount=Sum('actual_amount'))
+        return Formater.formatAmountShowWithTwoDecimalPlaces(sum_rows['sum_amount'] or 0)
+
+    @staticmethod
+    def factory(user, data, id=None):
+        if id:
+            instance = PurchaseOrder.getById(id)
+        else:
+            instance = None
+        serializer = PurchaseOrderSerializer(instance, data=data)
+        serializer.user = user
+        return serializer
+
+    def create(self, validated_data):
+        validated_data['create_user'] = self.user
+        validated_data['department'] = self.user.department
+        order = PurchaseOrder.objects.create(**validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加采购合同[%s],id=%d" % (order.no, order.id),
+            validated_data
+        )
+        return order
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+    def update(self, instance, validated_data):
+        instance = super(PurchaseOrderSerializer, self).update(instance, validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.UPDATE,
+            u"修改采购合同[%s],id=%d" % (instance.no, instance.id),
+            validated_data
+        )
+        return instance
+
+class PurchaseOrderDetailSerializer(serializers.ModelSerializer):
+    name = serializers.CharField(source='product.name', read_only=True)
+    model = serializers.CharField(source='product.model', read_only=True)
+    supplier_name = serializers.CharField(source='main.supplier.name', read_only=True)
+    order_no = serializers.CharField(source='main.no', read_only=True)
+    count = CountShowCharField()
+    arrval_count = CountShowCharField(read_only=True)
+    price = PriceShowCharField()
+    amount = AmountShowCharField(read_only=True)
+    create_time = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    check_time = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    invoice_date = serializers.DateField(format=base.DATE_FORMAT, read_only=True)
+    create_user_text = serializers.CharField(source='create_user.name', read_only=True)
+    check_user_text = serializers.CharField(source='check_user.name', read_only=True)
+    invoice_amount = AmountShowCharField(read_only=True)
+    check_status_text = serializers.CharField(source='get_check_status_display', read_only=True)
+    images = serializers.SerializerMethodField()
+
+    class Meta:
+        model = PurchaseOrderDetail
+        fields = '__all__'
+
+    def get_images(self, obj):
+        data = []
+        rows = PurchaseInvoiceImage.objects.filter(order_detail=obj)
+        data.extend([s[0] for s in rows.values_list('invoice_image')])
+        return ','.join(data)
+
+    @staticmethod
+    def factory(user, data):
+        instances = None
+        serializer = PurchaseOrderDetailSerializer(instances, data=data)
+        serializer.user = user
+        return serializer
+
+    def create(self, validated_data):
+        order = PurchaseOrderDetail.objects.create(**validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加采购合同明细[%s],id=%d" % (order.main.no, order.id),
+            validated_data
+        )
+        return order
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+    def validate(self, data):
+        data['price'] = Formater.formatPrice(data['price'])
+        data['count'] = Formater.formatCount(data['count'])
+        data['amount'] = data['count'] * data['price']
+        return data
+
+    def update(self, instance, validated_data):
+        instance = super(PurchaseOrderDetailSerializer, self).update(instance, validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.UPDATE,
+            u"修改采购合同明细[%s],id=%d" % (instance.main.no, instance.id),
+            validated_data
+        )
+        return instance
+
+
+class GodownEntrySerializer(serializers.ModelSerializer):
+    status = serializers.CharField(read_only=True)
+    no = serializers.CharField(read_only=True)
+    status_text = serializers.CharField(source='get_status_display', read_only=True)
+    create_user_text = serializers.CharField(source='create_user.name', read_only=True)
+    department_text = serializers.CharField(source='department.name', read_only=True)
+    supplier_text = serializers.CharField(source='supplier.name', read_only=True)
+    total_count = CountShowCharField(read_only=True)
+    total_amount = AmountShowCharField(read_only=True)
+    total_invoice_amount = AmountShowCharField(read_only=True)
+    check_user_text = serializers.CharField(source='check_user.name', read_only=True)
+    check_time = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    create_time = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    warehouse_text = serializers.CharField(source='warehouse.name', read_only=True)
+    purchase_order_no = serializers.CharField(source='purchase_order.no', read_only=True)
+    class Meta:
+        model = GodownEntry
+        fields = '__all__'
+
+    @staticmethod
+    def factory(user, data, id=None):
+        if id:
+            instance = GodownEntry.getById(id)
+        else:
+            instance = None
+        serializer = GodownEntrySerializer(instance, data=data)
+        serializer.user = user
+        return serializer
+
+    def create(self, validated_data):
+        validated_data['create_user'] = self.user
+        validated_data['department'] = self.user.department
+        instance = GodownEntry.objects.create(**validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加入库单[%s],id=%d" % (instance.no, instance.id),
+            validated_data
+        )
+        return instance
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+    def update(self, instance, validated_data):
+        instance = super(GodownEntrySerializer, self).update(instance, validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.UPDATE,
+            u"修改入库单[%s],id=%d" % (instance.no, instance.id),
+            validated_data
+        )
+        return instance
+
+class GodownEntryDetailSerializer(serializers.ModelSerializer):
+    product_base_text = serializers.CharField(source='product_base.name', read_only=True)
+    product_base_model = serializers.CharField(source='product_base.model', read_only=True)
+    unit_text = serializers.CharField(source='product_base.unit', read_only=True)
+    count = CountShowCharField()
+    price = PriceShowCharField()
+    amount = AmountShowCharField(read_only=True)
+    invoice_amount = AmountShowCharField()
+
+    class Meta:
+        model = GodownEntryDetail
+        fields = '__all__'
+
+    @staticmethod
+    def factory(user, data):
+        serializer = GodownEntryDetailSerializer(None, data=data)
+        serializer.user = user
+        return serializer
+
+    def create(self, validated_data):
+        instance = GodownEntryDetail.objects.create(**validated_data)
+
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加入库明细[%s],id=%d" % (instance.product_base.name, instance.id),
+            validated_data
+        )
+        return instance
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+    def validate(self, data):
+        data['price'] = Formater.formatPrice(data['price'])
+        data['count'] = Formater.formatCount(data['count'])
+        data['amount'] = data['count'] * data['price']
+        data['invoice_amount'] = Formater.formatAmount(data['invoice_amount'])
+        warehouse_stock = WarehouseStock.getByWarehouseAndProduct(data['main'].warehouse, data['product_base'])
+        data['warehouse_stock'] = warehouse_stock
+        return data
+
+
+class GodownEntryReturnSerializer(serializers.ModelSerializer):
+    status = serializers.CharField(read_only=True)
+    no = serializers.CharField(read_only=True)
+    status_text = serializers.CharField(source='get_status_display', read_only=True)
+    create_user_text = serializers.CharField(source='create_user.name', read_only=True)
+    department_text = serializers.CharField(source='department.name', read_only=True)
+    supplier_text = serializers.CharField(source='supplier.name', read_only=True)
+    total_count = CountShowCharField(read_only=True)
+    total_amount = AmountShowCharField(read_only=True)
+    warehouse_text = serializers.CharField(source='warehouse.name', read_only=True)
+    check_user_text = serializers.CharField(source='check_user.name', read_only=True)
+    check_time = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    create_time = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+
+    class Meta:
+        model = GodownEntryReturn
+        fields = '__all__'
+
+    @staticmethod
+    def factory(user, data, id=None):
+        if id:
+            instance = GodownEntryReturn.getById(id)
+        else:
+            instance = None
+        serializer = GodownEntryReturnSerializer(instance, data=data)
+        serializer.user = user
+        return serializer
+
+    def create(self, validated_data):
+        validated_data['create_user'] = self.user
+        validated_data['department'] = self.user.department
+        instance = GodownEntryReturn.objects.create(**validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加退货单[%s],id=%d" % (instance.no, instance.id),
+            validated_data
+        )
+        return instance
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+    def update(self, instance, validated_data):
+        instance = super(GodownEntryReturnSerializer, self).update(instance, validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.UPDATE,
+            u"修改退货单[%s],id=%d" % (instance.no, instance.id),
+            validated_data
+        )
+        return instance
+
+
+class GodownEntryReturnDetailSerializer(serializers.ModelSerializer):
+    main_no = serializers.CharField(source='main.no', read_only=True)
+    main_supplier = serializers.CharField(source='main.supplier.name', read_only=True)
+    godownentry_no = serializers.CharField(source='godownentry_detail.main.no', read_only=True)
+    main_warehouse = serializers.CharField(source='main.warehouse.name', read_only=True)
+    main_create_user = serializers.CharField(source='main.create_user.name', read_only=True)
+    main_create_time = TimeCharField(source='main.create_time', read_only=True)
+    main_check_user = serializers.CharField(source='main.create_user.name', read_only=True)
+    main_check_time = TimeCharField(source='main.check_time.name', read_only=True)
+    product_base_text = serializers.CharField(source='product_base.name', read_only=True)
+    product_base_model = serializers.CharField(source='product_base.model', read_only=True)
+    main_notes = serializers.CharField(source='main.notes', read_only=True)
+
+    count = CountShowCharField()
+    price = PriceShowCharField(read_only=True)
+    amount = AmountShowCharField()
+
+    class Meta:
+        model = GodownEntryReturnDetail
+        fields = '__all__'
+
+    @staticmethod
+    def factory(user, data):
+        serializer = GodownEntryReturnDetailSerializer(None, data=data)
+        serializer.user = user
+        return serializer
+
+    def create(self, validated_data):
+        instance = GodownEntryReturnDetail.objects.create(**validated_data)
+
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加退货明细[%s],id=%d" % (instance.product_base.name, instance.id),
+            validated_data
+        )
+        return instance
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+    def validate(self, data):
+        data['count'] = Formater.formatCount(data['count'])
+        data['amount'] = Formater.formatAmount(data['amount'])
+        data['price'] = data['amount'] / data['count']
+        warehouse_stock = WarehouseStock.getByWarehouseAndProduct(data['main'].warehouse, data['product_base'])
+        data['warehouse_stock'] = warehouse_stock
+        return data
+
+
+class PurchasePaymentSerializer(serializers.ModelSerializer):
+    create_time = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    check_time = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    status_text = serializers.CharField(source='get_status_display', read_only=True)
+    check_user_text = serializers.CharField(source='check_user.name', read_only=True)
+    create_user_text = serializers.CharField(source='create_user.name', read_only=True)
+    supplier_name = serializers.CharField(source='order.supplier.name', read_only=True)
+    supplier_account = serializers.CharField(source='order.supplier.account', read_only=True)
+    supplier_bank = serializers.CharField(source='order.supplier.opening_bank', read_only=True)
+    order_no = serializers.CharField(source='order.no', read_only=True)
+    payment = BooleanCharField(source='is_pay', read_only=True)
+    amount = AmountShowCharFieldWithTwoDecimalPlaces()
+    actual_amount = AmountShowCharFieldWithTwoDecimalPlaces(read_only=True)
+    apply_amount = AmountShowCharFieldWithTwoDecimalPlaces()
+    amount_CNY = serializers.SerializerMethodField()
+    actual_amount_CNY = serializers.SerializerMethodField()
+    apply_amount_CNY = serializers.SerializerMethodField()
+
+    review_user_text = serializers.CharField(source='review_user.name', read_only=True)
+    review_time = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    ratify_user_text = serializers.CharField(source='ratify_user.name', read_only=True)
+    ratify_time = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+
+    class Meta:
+        model = PurchasePayment
+        fields = '__all__'
+
+    def get_amount_CNY(self, obj):
+        retval = Formater.formatAmountShow(obj.amount)
+        return utils.number2CNY(float(retval))
+
+    def get_actual_amount_CNY(self, obj):
+        retval = Formater.formatAmountShow(obj.actual_amount)
+        return utils.number2CNY(float(retval))
+
+    def get_apply_amount_CNY(self, obj):
+        retval = Formater.formatAmountShow(obj.apply_amount)
+        return utils.number2CNY(float(retval))
+
+    @staticmethod
+    def factory(user, data, id=None):
+        if id:
+            instance = PurchasePayment.getById(id)
+        else:
+            instance = None
+        serializer = PurchasePaymentSerializer(instance, data=data)
+        serializer.user = user
+        return serializer
+
+    def create(self, validated_data):
+        validated_data['create_user'] = self.user
+        validated_data['department'] = self.user.department
+        order = PurchasePayment.objects.create(**validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加采购付款单[%s],id=%d" % (order.no, order.id),
+            validated_data
+        )
+        return order
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))
+
+    def update(self, instance, validated_data):
+        instance = super(PurchasePaymentSerializer, self).update(instance, validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.UPDATE,
+            u"修改采购付款单[%s],id=%d" % (instance.no, instance.id),
+            validated_data
+        )
+        return instance
+
+
+class PurchasePaySerializer(serializers.ModelSerializer):
+    payment_type_text = serializers.CharField(source='get_status_display', read_only=True)
+    payment_time = serializers.DateTimeField(format=base.DATETIME_FORMAT, read_only=True)
+    payment_user_text = serializers.CharField(source='payment_user.name', read_only=True)
+
+    class Meta:
+        model = PurchasePay
+        fields = '__all__'
+
+    @staticmethod
+    def factory(user, data, id=None):
+        if id:
+            instance = PurchasePay.getById(id)
+        else:
+            instance = None
+        serializer = PurchasePaySerializer(instance, data=data)
+        serializer.user = user
+        return serializer
+
+    def create(self, validated_data):
+        validated_data['payment_user'] = self.user
+        validated_data['payment_department'] = self.user.department
+        order = PurchasePay.objects.create(**validated_data)
+        BizLog.objects.addnew(
+            self.user,
+            BizLog.INSERT,
+            u"添加采购付款单明细id=%d" % (order.id),
+            validated_data
+        )
+        return order
+
+    def validSave(self):
+        if self.is_valid():
+            return self.save()
+        else:
+            raise CustomError(dump_serializer_errors(self))

+ 88 - 0
apps/purchase/urls.py

@@ -0,0 +1,88 @@
+#coding=utf-8
+
+from django.conf.urls import url
+
+from views import *
+
+urlpatterns = (
+    url(r'^purchase_order/data/$', purchase_order_list),
+    url(r'^purchase_order/export/$', purchase_order_export),
+    url(r'^purchase_order/save/$', purchase_order_save),
+    url(r'^purchase_order/edit/$', purchase_order_edit),
+    url(r'^purchase_order/plan_to_order/$', purchase_plan_to_order),
+    url(r'^purchase_order/detail/$', purchase_order_detail),
+    url(r'^select_purchase_order/$', select_purchase_order),
+    url(r'^purchase_order/delete/$', purchase_order_delete),
+    url(r'^purchase_order/check/$', purchase_order_check),
+    url(r'^purchase_order/export_detail/$', purchase_order_export_detail),
+
+    url(r'^purchase/data/$', purchase_list),
+    url(r'^purchase/export/$', purchase_export),
+    url(r'^purchase/price_export/$', purchase_price_export),
+    url(r'^purchase/import/$', purchase_import),
+    url(r'^purchase/save/$', purchase_save),
+    url(r'^purchase/check/$', purchase_check),
+    url(r'^purchase/delete/$', purchase_delete),
+    url(r'^purchase/detail/$', purchase_detail),
+    url(r'^purchase_user/save/$', purchase_user_save),
+
+    url(r'^purchase/table_totle/$', purchase_table),# 动态添加表头以及数据
+
+    url(r'^purchase_price/data/$', purchase_price_list),
+    url(r'^purchase_price/save/$', purchase_price_save),
+    url(r'^purchase_price/report/$', purchase_price_report),
+    url(r'^purchase_price/detail/$', purchase_price_detail),
+    url(r'^purchase_price/edit_save/$', purchase_price_edit_save),
+
+    url(r'^purchase_payment/data/$', purchase_payment_list),
+    url(r'^purchase_payment/export/$', purchase_payment_export),
+    url(r'^purchase_payment/save/$', purchase_payment_save),
+    url(r'^purchase_payment/detail/$', purchase_payment_detail),
+    url(r'^purchase_payment/delete/$', purchase_payment_delete),
+    url(r'^purchase_payment/check/$', purchase_payment_check),
+    url(r'^purchase_payment/review/$', purchase_payment_review),
+    url(r'^purchase_payment/ratify/$', purchase_payment_ratify),
+    url(r'^purchase_payment/pay/$', purchase_payment_pay),
+    url(r'^purchase_payment/pay/detail/$', purchase_payment_pay_detail),
+    url(r'^purchase_payment/export_detail/$', purchase_payment_export_detail),
+
+    url(r'^purchase_invoice/data/$', purchase_invoice_list),
+    url(r'^purchase_invoice/export/$', purchase_invoice_export),
+    url(r'^purchase_invoice/save/$', purchase_invoice_save),
+    url(r'^purchase_invoice/check/$', purchase_invoice_check),
+    url(r'^purchase_invoice/upload_image/$', purchase_invoice_upload_image),
+    url(r'^purchase_invoice/images/$', purchase_invoice_images),
+    url(r'^purchase_invoice/del_image/$', purchase_invoice_del_image),
+
+    url(r'^search_product/$', search_product),
+    url(r'^search_user/$', search_user),
+    url(r'^search_price/$', search_price),
+
+    url(r'^godownentry/data/$', godownentry_list),
+    url(r'^godownentry/save/$', godownentry_save),
+    url(r'^godownentry/delete/$', godownentry_delete),
+    url(r'^godownentry/detail/$', godownentry_detail),
+    url(r'^godownentry/export/$', godownentry_export),
+    url(r'^godownentry/check/$', godownentry_check),
+    url(r'^godownentry/import/$', godownentry_import),
+    url(r'^godownentry/export_detail/$', godownentry_export_detail),
+    url(r'^godownentry/senior_edit/$', godownentry_senior_edit),
+
+    url(r'^godownentry_query/data/$', godownentry_query_list),
+    url(r'^godownentry_query/export/$', godownentry_query_export),
+    url(r'^godownentry_query/detail/$', godownentry_query_detail),
+
+    url(r'^godownentry_return/data/$', godownentry_return_list),
+    url(r'^godownentry_return/save/$', godownentry_return_save),
+    url(r'^godownentry_return/check/$', godownentry_return_check),
+    url(r'^godownentry_return/delete/$', godownentry_return_delete),
+    url(r'^godownentry_return/detail/$', godownentry_return_detail),
+    url(r'^godownentry_return/export/$', godownentry_return_export),
+    url(r'^godownentry_return/export_detail/$', godownentry_return_export_detail),
+    url(r'^get_godownentry_detail_data/$', get_godownentry_detail_data), # 入库查询转退货时用, 从入库明细退货
+
+    url(r'^godownentry_return_query/data/$', godownentry_return_query_list),
+    url(r'^godownentry_return_query/export/$', godownentry_return_query_export),
+    url(r'^godownentry_return_query/detail/$', godownentry_return_query_detail),
+
+)

+ 2845 - 0
apps/purchase/views.py

@@ -0,0 +1,2845 @@
+# coding=utf-8
+import traceback
+import json
+import os
+from collections import OrderedDict
+from datetime import timedelta
+
+from django.db.models import Q, Count, Sum, F
+from apps.base import Formater
+from _mysql_exceptions import IntegrityError
+from django.db.models import ProtectedError
+from django.utils import timezone
+from django.views.decorators.csrf import csrf_exempt
+from django.db import transaction, IntegrityError
+from apps.warehouse.biz import BizWarehouse, GetWarehouseSrockRecord
+from libs import utils
+from django.conf import settings
+from libs.http import JSONResponse, JSONError, DataGridJSONResponse
+from apps.exceptions import CustomError, ExportChange
+from apps.account.decorators import token_required, permission_required, valid_permission, isHasPermissions
+
+from apps.account.models import User
+from apps.foundation.models import BizLog, Option
+from apps.product.models import ProductBase
+from apps.warehouse.models import WarehouseRecord, Warehouse, WarehouseStockRecord, WarehouseRecordDetail
+from apps.warehouse.models import WarehouseStock, InventoryDetail
+from apps.goods.models import GoodsGodownEntryDetail
+from apps.material.models import DeliverDetail, DeliverReturnDetail
+from apps.supplier.models import Supplier
+
+from apps.purchase.filters import PurchasePlanFilter, PurchaseOrderFilter, PurchasePriceFilter, GodownEntryFilter, \
+    PurchasePaymentFilter, PurchaseOrderDetailFilter, GodownEntryReturnFilter, GodownEntryReturnDetailFilter, PurchasePriceExportFilter
+from apps.purchase.models import PurchasePlan, PurchasePlanDetail, PurchaseOrder, PurchaseOrderDetail, PurchasePrice, \
+    GodownEntryReturn, GodownEntryReturnDetail, PurchaseInvoiceImage, PurchasePay
+from apps.purchase.resources import GodownEntryResource, GodownEntryDetailResource, GodownEntryImporter, \
+    PurchasePlanResource, PurchasePlanDetailResource, PurchaseOrderResource, PurchaseOrderDetailResource, \
+    PurchasePaymentResource, PurchasePaymentDetailResource, PurchaseInvoiceResource, GodownEntryQueryResource, \
+    GodownEntryReturnResource, GodownEntryReturnDetailResource, GodownEntryReturnQueryResource, PurchasePlanImporter, PurchasePriceResource
+from apps.purchase.serializers import PurchaseUser, GodownEntryDetail, GodownEntry, PurchasePaymentDetail, \
+    PurchasePayment, PurchasePlanSerializer, PurchasePlanDetailSerializer, PurchaseOrderDetailSerializer, \
+    PurchaseUserSerializer, PurchasePriceSerializer, GodownEntrySerializer, GodownEntryDetailSerializer, \
+    PurchaseOrderSerializer, PurchasePaymentSerializer, GodownEntryReturnSerializer, GodownEntryReturnDetailSerializer, \
+    PurchasePaySerializer
+
+
+@csrf_exempt
+@permission_required('purchase.view_purchase_plan')
+def purchase_list(request):
+    department_ids = request.user.getSubDepartmentIds()
+    user_ids = request.user.getSubEmployeeIds()
+    rows = PurchasePlan.objects.filter(Q(create_user_id__in=user_ids) | Q(department_id__in=department_ids) | Q(create_user=request.user))
+    f = PurchasePlanFilter(request.GET, queryset=rows)
+    total_row = f.qs.aggregate(total_count=Sum('total_count'))
+    more = {
+        'total_count': Formater.formatCountShow(total_row['total_count'])
+    }
+    rows, total = utils.get_page_data(request, f.qs)
+    serializer = PurchasePlanSerializer(rows, many=True)
+    return DataGridJSONResponse(serializer.data, total, more)
+
+@csrf_exempt
+@permission_required('purchase.export_purchase_plan')
+def purchase_export(request):
+    id = request.GET.get('id')
+    if id:
+        production = PurchasePlan.getById(id)
+        goods_rows = PurchasePlanDetail.objects.filter(purchase__id=production.id)
+        serializer = PurchasePlanDetailSerializer(goods_rows, many=True)
+        export_data = ExportChange.dict_to_obj(serializer)
+        export_data = PurchasePlanDetailResource().export(export_data)
+        filename = utils.attachment_save(export_data)
+        return JSONResponse({'filename': filename})
+
+    department_ids = request.user.getSubDepartmentIds()
+    user_ids = request.user.getSubEmployeeIds()
+    rows = PurchasePlan.objects.filter(Q(create_user_id__in=user_ids) | Q(department_id__in=department_ids) | Q(create_user=request.user))
+    f = PurchasePlanFilter(request.GET, queryset=rows)
+    serializer = PurchasePlanSerializer(f.qs, many=True)
+    export_data = ExportChange.dict_to_obj(serializer)
+    export_data = PurchasePlanResource().export(export_data)
+    filename = utils.attachment_save(export_data)
+    return JSONResponse({'filename': filename})
+
+@csrf_exempt
+@permission_required('purchase.export_purchase_plan_price')
+def purchase_price_export(request):
+    id = request.GET.get('id')
+
+    rows = PurchasePrice.objects.filter(purchase_detail__purchase_id=id,report=True)
+    if rows.count() == 0:
+        return JSONError(u'该采购单没有上传价格')
+    f = PurchasePriceExportFilter(request.GET, queryset=rows)
+    serializer = PurchasePriceSerializer(f.qs, many=True)
+    export_data = ExportChange.dict_to_obj(serializer)
+    export_data = PurchasePriceResource().export(export_data)
+    filename = utils.attachment_save(export_data)
+    return JSONResponse({'filename': filename})
+
+@csrf_exempt
+@permission_required('purchase.add_purchase_plan')
+def purchase_import(request):
+    file = request.FILES.get('excel_file')
+    main_data = json.loads(request.POST.get('main_data'))
+
+    try:
+        line = 2
+        importer = PurchasePlanImporter()
+        excel_rows = importer.getExcelData(file)
+        with transaction.atomic():
+            serializer = PurchasePlanSerializer.factory(request.user, main_data)
+            serializer = serializer.validSave()
+            for excel_row in excel_rows:
+                try:
+                    row = importer.validRow(excel_row)
+                    type = ProductBase.CONSUMABLE
+                    if row[u'产品类别'] == u'原料':
+                        type = ProductBase.MATERIAL
+
+                    model = row[u'产品代码']
+                    product_base = ProductBase.objects.filter(model=model, type=type)
+                    if product_base.count() == 0:
+                        raise CustomError(u'产品代码不存在')
+                    elif product_base.count() > 1:
+                        raise CustomError(u'产品代码重复,前往基础数据设置修改')
+                    else:
+                        product_base = product_base.first()
+
+                    # quality_request = None
+                    # if row[u'质量要求']:
+                    #     quality_request = Option.getByName(row[u'质量要求'], Option.QUALITY_REQUEST).id
+
+                    items_data = {}
+                    items_data['purchase'] = serializer.id
+                    items_data['quality_request_text'] = row[u'质量要求']
+                    items_data['product'] = product_base.id
+                    items_data['purchase_count'] = row[u'数量']
+                    items_data['product_time'] = row[u'需求时间']
+                    items_data['notes'] = row[u'备注']
+
+                    detail_serializer = PurchasePlanDetailSerializer.factory(request.user, items_data)
+                    detail_serializer.validSave()
+                except CustomError, e:
+                    raise CustomError(u'第%d行:%s' % (line, e.get_error_msg()))
+                except Exception, e:
+                    raise CustomError(u'第%d行:%s' % (line, unicode(e)))
+                line += 1
+            serializer.updateTotalCount()
+            BizLog.objects.addnew(request.user, BizLog.IMPORT, u"导入采购计划[%s],id=%d" % (serializer.no, serializer.id))
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'导入失败!')
+    return JSONResponse()
+
+@csrf_exempt
+@permission_required('purchase.add_purchase_plan')
+def purchase_save(request):
+    id = request.GET.get('id')
+    purchase_base_data = json.loads(request.POST.get('items'))
+    purchase_data = json.loads(request.POST.get('purchase'))
+    try:
+        with transaction.atomic():
+            pb = PurchasePlanSerializer.factory(request.user, purchase_data, id)
+            if pb.instance and pb.instance.status == settings.PASS:
+                raise CustomError(u'审核通过,禁止修改')
+            pb = pb.validSave()
+            PurchasePlanDetail.objects.filter(purchase=pb).delete()
+            for purchase_base in purchase_base_data:
+                purchase_base['purchase'] = pb.id
+                purchase_base['product'] = purchase_base['id']
+                if 'product_time' in purchase_base and not purchase_base['product_time']:
+                    purchase_base['product_time'] = None
+                production = PurchasePlanDetailSerializer.factory(request.user, purchase_base)
+                production.validSave()
+            pb.updateTotalCount()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'保存失败')
+
+    return JSONResponse()
+
+
+@csrf_exempt
+@permission_required('purchase.check_purchase_plan')
+def purchase_check(request):
+    id = request.GET.get('id')
+    #c_type = request.GET.get('c_type')
+    status = int(request.GET.get('status'))
+    try:
+        with transaction.atomic():
+            purchase = PurchasePlan.getById(id)
+            if status == settings.PASS:
+                if purchase.status == settings.PASS:
+                    raise CustomError(u'该采购计划已审核')
+                purchase.status = settings.PASS
+                purchase.check_user = request.user
+                purchase.check_time = timezone.now()
+                BizLog.objects.addnew(
+                    request.user,
+                    BizLog.CHECK,
+                    u"审核采购计划[%s],id=%d" % (purchase.name, purchase.id),
+                )
+            else:
+                if purchase.status == settings.DEFAULT:
+                    raise CustomError(u'该采购计划未审核')
+                # if purchase.check_user2:
+                #     raise CustomError(u'该采购计划已复核')
+                is_report = purchase.isReport()
+                is_compact = purchase.isCompart()
+                if is_compact:
+                    raise CustomError(u'已生成采购合同,禁止撤销审核')
+                if is_report:
+                    raise CustomError(u'已有上报的询价记录,禁止撤销审核')
+                purchaseprices = PurchasePrice.objects.filter(purchase_detail__purchase=purchase)
+                purchaseprices.delete()
+                PurchaseUser.objects.filter(purchase=purchase).delete()
+                purchase.status = settings.DEFAULT
+                purchase.check_user = None
+                purchase.check_time = None
+                BizLog.objects.addnew(
+                    request.user,
+                    BizLog.CHECK,
+                    u"撤消审核采购计划[%s],id=%d" % (purchase.name, purchase.id),
+                )
+            purchase.save()
+            # if c_type == 'check':
+            #     # 审核
+            #
+            # elif c_type == 'check2':
+            #     #复核
+            #     if status == settings.PASS:
+            #         if purchase.status == settings.DEFAULT:
+            #             raise CustomError(u'该采购计划未审核')
+            #         if purchase.check_user3:
+            #             raise CustomError(u'该采购计划已批准')
+            #
+            #         purchase.check_user2 = request.user
+            #         purchase.check_time2 = timezone.now()
+            #         BizLog.objects.addnew(
+            #             request.user,
+            #             BizLog.CHECK,
+            #             u"复核采购计划[%s],id=%d" % (purchase.name, purchase.id),
+            #         )
+            #     else:
+            #         if not purchase.check_user2:
+            #             raise CustomError(u'该采购计划未复核')
+            #         if purchase.check_user3:
+            #             raise CustomError(u'该采购计划已批准')
+            #
+            #         purchase.check_user2 = None
+            #         purchase.check_time2 = None
+            #         BizLog.objects.addnew(
+            #             request.user,
+            #             BizLog.CHECK,
+            #             u"撤消复核采购计划[%s],id=%d" % (purchase.name, purchase.id),
+            #         )
+            # elif c_type == 'check3':
+            #     #批准
+            #     if status == settings.PASS:
+            #         if not purchase.check_user2:
+            #             raise CustomError(u'该采购计划未复核')
+            #         if purchase.check_user3:
+            #             raise CustomError(u'该采购计划已批准')
+            #         purchase.status = status
+            #         purchase.check_user3 = request.user
+            #         purchase.check_time3 = timezone.now()
+            #         BizLog.objects.addnew(
+            #             request.user,
+            #             BizLog.CHECK,
+            #             u"批准采购计划[%s],id=%d" % (purchase.name, purchase.id),
+            #         )
+            #     else:
+            #         if not purchase.check_user3:
+            #             raise CustomError(u'该采购计划未批准')
+            #         is_report = purchase.isReport()
+            #         is_compact = purchase.isCompart()
+            #         if is_compact:
+            #             raise CustomError(u'已生成采购合同,禁止撤销')
+            #         if is_report:
+            #             raise CustomError(u'已有上报的询价记录,禁止撤销')
+            #
+            #         purchase.status = settings.CHECKING
+            #         purchase.check_user3 = None
+            #         purchase.check_time3 = None
+            #         BizLog.objects.addnew(
+            #             request.user,
+            #             BizLog.CHECK,
+            #             u"撤消批准采购计划[%s],id=%d" % (purchase.name, purchase.id),
+            #         )
+
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'审核失败')
+    return JSONResponse({})
+
+
+@csrf_exempt
+@permission_required('purchase.delete_purchase_plan')
+def purchase_delete(request):
+    id = int(request.GET.get('id'))
+    try:
+        with transaction.atomic():
+            purchase = PurchasePlan.getById(id)
+            if purchase.status == settings.PASS:
+                raise CustomError(u'该采购计划已审核,禁止删除')
+            is_report = purchase.isReport()
+            is_compact = purchase.isCompart()
+            if is_compact:
+                raise CustomError(u'已生成采购合同,禁止撤销审核')
+            if is_report:
+                raise CustomError(u'已有上报的询价记录,禁止撤销审核')
+            BizLog.objects.addnew(request.user, BizLog.DELETE, u"删除采购计划[%s],id=%d" % (purchase.name, purchase.id))
+            PurchasePlanDetail.objects.filter(purchase__id=purchase.id).delete()
+            purchase.delete()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except ProtectedError:
+        return JSONError(u'该计划已被引用,禁止删除')
+    except IntegrityError:
+        return JSONError(u'该计划已被引用,禁止删除')
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'删除失败!')
+
+    return JSONResponse({})
+
+
+@csrf_exempt
+@permission_required('purchase.view_purchase_plan')
+def purchase_detail(request):
+    id = request.GET.get('id')
+    purchase = PurchasePlan.getById(id)
+    company = purchase.department.getCompany()
+    product_rows = PurchasePlanDetail.objects.filter(purchase__id=purchase.id)
+    data = {
+        'purchase': [],
+        'product_items': []
+    }
+    if purchase.status == settings.PASS:
+        status_text = u'已审核'
+    else:
+        status_text = u'待审核'
+    production_item = {
+        'name': purchase.name,
+        'no': purchase.no,
+        'company': company.name,
+        'status_text': status_text,
+        'notes': purchase.notes,
+        'demend_user_id': purchase.demend_user and purchase.demend_user.id,
+        'demend_user': purchase.demend_user and purchase.demend_user.name or '',
+        'check_user': purchase.check_user and purchase.check_user.name or '',
+        'check_time': Formater.formatStrTime(purchase.check_time),
+        'create_time': Formater.formatStrTime(purchase.create_time),
+    }
+    data['purchase'].append(production_item)
+    for row in product_rows:
+        count = row.purchase_count - row.product.stock_count
+        if count < 0:
+            count = 0
+
+        item = {
+            'id': row.id,
+            'product_id': row.product.id,
+            'name': row.product.name,
+            'model': row.product.model,
+            'unit': row.product.unit,
+            'standard': row.product.standard or '',
+            'product_notes': row.product.notes or '',
+            'quality_request_text': row.quality_request_text or '',
+            'count': Formater.formatCountShow(count),
+            'stock_count': Formater.formatCountShow(row.product.stock_count),
+            'purchase_count': Formater.formatCountShow(row.purchase_count),
+            'product_time': Formater.formatStrTimeS(row.product_time),
+            'product_time1': Formater.formatStrDate(row.product_time),
+            'notes': row.notes or ''
+        }
+        data['product_items'].append(item)
+    return JSONResponse(data)
+
+
+@csrf_exempt
+@permission_required('purchase.add_purchase_user_plan')
+def purchase_user_save(request):
+    id = request.GET.get('id')
+    touch = request.GET.get('touch')
+    if touch:
+        users = json.loads(request.body)['users']
+    else:
+        users = json.loads(request.POST.get('users'))
+
+    users_list = []
+    for user in users:
+        users_list.append(user['id'])
+    try:
+        purchase_data = PurchasePlan.getById(id)
+        purchase_details = purchase_data.getPurchaseDetails()
+        # 对比采购员名单,多的添加,少的删除
+        purchase_user_datas = PurchaseUser.objects.filter(purchase=purchase_data).values_list('purchase_user_id',flat=True)
+        purchase_user_adds = [val for val in users_list if val not in purchase_user_datas]
+
+        purchase_user_deletes = []
+        report_users = PurchasePrice.objects.filter(purchase_detail__purchase=purchase_data,report=True).values_list('purchase_user_id',flat=True).order_by('purchase_user_id').distinct()
+        for val in purchase_user_datas:
+            if val not in users_list and val not in report_users:
+                purchase_user_deletes.append(val)
+
+        with transaction.atomic():
+            PurchasePrice.objects.filter(purchase_user__in=purchase_user_deletes, purchase_detail__purchase=purchase_data).delete()
+            PurchaseUser.objects.filter(purchase=purchase_data, purchase_user__id__in=purchase_user_deletes).delete()
+            for purchase_user_add in purchase_user_adds:
+                purchase_user_data = {
+                    'purchase': purchase_data.id,
+                    'purchase_user': purchase_user_add,
+                    }
+                production = PurchaseUserSerializer.factory(request.user, purchase_user_data)
+                production.validSave()
+                for purchase_detail in purchase_details:
+                    data = {
+                        'purchase_detail': purchase_detail.id,
+                        'purchase_user': purchase_user_add,
+                        'report': False,
+                        'price': 0,
+                    }
+                    item = PurchasePriceSerializer.factory(request.user, data)
+                    item.validSave()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'保存失败!')
+
+    return JSONResponse()
+
+@csrf_exempt
+@token_required
+def purchase_table(request):
+    id = request.GET.get('id')
+    purchaseplan = PurchasePlan.getById(id)
+    data = {
+        'plan_details': [],
+        'purchase_users': [],
+        'purchase_prices':[]
+    }
+
+    purchasedetails = PurchasePlanDetail.objects.filter(purchase=purchaseplan)
+    for purchasedetail in purchasedetails:
+        item = {
+            'id':purchasedetail.id,
+            'name': purchasedetail.product.name,
+            'model': purchasedetail.product.model,
+            'unit': purchasedetail.product.unit,
+            'count': Formater.formatCountShow(purchasedetail.purchase_count),
+        }
+        data['plan_details'].append(item)
+
+    purchaseusers = PurchaseUser.objects.filter(purchase=purchaseplan)
+    for purchaseuser in purchaseusers:
+        item = {
+            'id':purchaseuser.purchase_user.id,
+            'name':purchaseuser.purchase_user.name,
+            'plan_id':purchaseuser.purchase_id
+        }
+        data['purchase_users'].append(item)
+
+    purchaseprices = PurchasePrice.objects.filter(purchase_detail__purchase=purchaseplan,report=True)
+    for purchaseprice in purchaseprices:
+        item = {
+            'id': purchaseprice.id,
+            'user_id': purchaseprice.purchase_user_id,
+            'detail_id': purchaseprice.purchase_detail_id,
+            'supplier': purchaseprice.supplier.name,
+            'price': Formater.formatPriceShow(purchaseprice.price),
+            'notes': purchaseprice.notes,
+        }
+        data['purchase_prices'].append(item)
+
+    return JSONResponse(data)
+
+
+@csrf_exempt
+@permission_required('purchase.view_purchase_user_plan')
+def purchase_price_list(request):
+    f = PurchasePriceFilter(request.GET, queryset=PurchasePrice.objects.filter(
+        Q(purchase_user=request.user) & Q(purchase_detail__is_compact=False)))
+    rows, total = utils.get_page_data(request, f.qs)
+    serializer = PurchasePriceSerializer(rows, many=True)
+    return DataGridJSONResponse(serializer.data, total)
+
+@csrf_exempt
+@permission_required('purchase.add_purchase_price_plan')
+def purchase_price_save(request):
+    id = request.GET.get('id')
+    data = json.loads(request.body)
+    try:
+        if 'tax_rate' in data and data['tax_rate'] == '':
+            data['tax_rate'] = None
+        purchase_price = PurchasePrice.getById(id)
+        if purchase_price.report:
+            raise CustomError(u'询价已上报,禁止录入')
+        with transaction.atomic():
+            production = PurchasePriceSerializer.factory(request.user, data, id)
+            production.validSave()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'保存失败!')
+
+    return JSONResponse()
+
+
+@csrf_exempt
+@permission_required('purchase.add_purchase_price_plan')
+def purchase_price_report(request):
+    try:
+        with transaction.atomic():
+            purchase_prices = PurchasePrice.objects.filter(purchase_user=request.user, supplier__isnull=False)
+            purchase_price_ids = purchase_prices.values_list('id', flat=True)
+            purchase_prices.update(report=True)
+            BizLog.objects.addnew(
+                request.user,
+                BizLog.UPDATE,
+                u"上报询价",
+                purchase_price_ids
+            )
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'上报失败!')
+    return JSONResponse()
+
+@csrf_exempt
+@token_required
+def purchase_price_detail(request):
+    id = request.GET.get('id')
+
+    rows = PurchasePrice.objects.filter(purchase_detail__purchase_id=id, report=True)
+
+    data = []
+    for row in rows:
+        item = {
+            'id': row.id,
+            'name': row.purchase_detail.product.name,
+            'supplier_name': row.supplier.name,
+            'user_name': row.purchase_user.name,
+            'price': Formater.formatPriceShow(row.price),
+        }
+        data.append(item)
+    return JSONResponse({'data': data})
+
+
+@csrf_exempt
+@permission_required('purchase.edit_purchase_price')
+def purchase_price_edit_save(request):
+    data = json.loads(request.POST.get('data'))
+    try:
+        with transaction.atomic():
+           for item in data:
+               order = PurchasePrice.objects.filter(id=int(item['id'])).first()
+               if not order:
+                   raise CustomError(u'未找到单据')
+
+               order.price = Formater.formatPrice(item['price'])
+               order.save()
+
+           BizLog.objects.addnew(
+               request.user,
+               BizLog.UPDATE,
+               u"修改询价",
+               order.purchase_detail.purchase.no
+           )
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'保存失败')
+    return JSONResponse({})
+
+@csrf_exempt
+@permission_required('purchase.view_purchase_order')
+def purchase_order_list(request):
+    department_ids = request.user.getSubDepartmentIds()
+    user_ids = request.user.getSubEmployeeIds()
+    rows = PurchaseOrder.objects.filter(Q(create_user_id__in=user_ids) | Q(department_id__in=department_ids) | Q(create_user=request.user))
+    f = PurchaseOrderFilter(request.GET, queryset=rows)
+    total_row = f.qs.aggregate(sum_count=Sum('count'), sum_amount=Sum('amount'))
+    more = {
+        'sum_count': Formater.formatCountShow(total_row['sum_count']),
+        'sum_amount': Formater.formatAmountShowWithTwoDecimalPlaces(total_row['sum_amount'])
+    }
+
+    rows, total = utils.get_page_data(request, f.qs)
+    serializer = PurchaseOrderSerializer(rows, many=True)
+    return DataGridJSONResponse(serializer.data, total, more)
+
+@csrf_exempt
+@permission_required('purchase.export_purchase_order')
+def purchase_order_export(request):
+    department_ids = request.user.getSubDepartmentIds()
+    user_ids = request.user.getSubEmployeeIds()
+    rows = PurchaseOrder.objects.filter(Q(create_user_id__in=user_ids) | Q(department_id__in=department_ids) | Q(create_user=request.user))
+    f = PurchaseOrderFilter(request.GET, queryset=rows)
+    serializer = PurchaseOrderSerializer(f.qs, many=True)
+    export_data = ExportChange.dict_to_obj(serializer)
+    export_data = PurchaseOrderResource().export(export_data)
+    filename = utils.attachment_save(export_data)
+    BizLog.objects.addnew(request.user, BizLog.EXPORT, u"导出采购合同")
+    return JSONResponse({'filename': filename})
+
+
+@csrf_exempt
+@permission_required('purchase.edit_purchase_order')
+def purchase_order_edit(request):
+    id = request.GET.get('id')
+    data = json.loads(request.body)
+
+    try:
+        with transaction.atomic():
+
+            order = PurchaseOrder.objects.filter(id=id).first()
+            if order.status > PurchaseOrder.DRAFT:
+                raise CustomError(u'该合同已审核,禁止修改')
+
+            order.supplier_id = data['supplier']
+            order.payment_type = data['payment_type']
+            order.notes = data['notes']
+            order.deliver_time = data['deliver_time']
+            order.no = data['no']
+            order.consignee_name = data['consignee_name']
+            order.consignee_tel = data['consignee_tel']
+            order.save()
+
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'保存失败')
+    return JSONResponse({})
+
+
+@csrf_exempt
+@permission_required('purchase.add_purchase_order | purchase.senior_purchase_order')
+def purchase_order_save(request):
+    id = request.GET.get('id')
+    data = json.loads(request.body)
+
+    try:
+        with transaction.atomic():
+            pb = PurchaseOrderSerializer.factory(request.user, data['order_data'], id)
+            if pb.instance and pb.instance.status == PurchaseOrder.TAKE_EFFECT:
+                raise CustomError(u'该合同已审核,禁止修改')
+            pb = pb.validSave()
+            PurchaseOrderDetail.objects.filter(main_id=pb.id).delete()
+            for item in data['items']:
+                item['main'] = pb.id
+                pbd = PurchaseOrderDetailSerializer.factory(request.user, item)
+                pbd.validSave()
+
+            pb.updateAmount()
+
+            if pb.plan:
+                pb.plan.updateCompact()
+
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'保存失败')
+    return JSONResponse({})
+
+@csrf_exempt
+@permission_required('purchase.add_purchase_order')
+def purchase_plan_to_order(request):
+    touch = request.GET.get('touch')
+    if touch:
+        data = json.loads(request.body)['data']
+    else:
+        data = json.loads(request.POST.get('data'))
+
+    try:
+        with transaction.atomic():
+            price_ids = [row['user_price_id'] for row in data]
+            supplier_rows = PurchasePrice.objects.filter(id__in=price_ids).values('supplier_id','purchase_user_id','purchase_user__department_id').order_by('supplier_id','purchase_user_id','purchase_user__department_id').distinct()
+            for supplier_row in supplier_rows:
+                order = PurchaseOrder.objects.create(
+                    supplier_id=supplier_row['supplier_id'],
+                    create_user_id=supplier_row['purchase_user_id'],
+                    department_id=supplier_row['purchase_user__department_id']
+                )  # 生成采购合同
+
+                purchase_plan = None
+                price_rows = PurchasePrice.objects.filter(supplier_id=supplier_row['supplier_id'], purchase_user_id=supplier_row['purchase_user_id'], id__in=price_ids)
+                if price_rows.count > 0:
+                    purchase_plan = price_rows[0].purchase_detail.purchase
+
+                for price_row in price_rows:
+                    if price_row.purchase_detail.is_compact:
+                        raise CustomError(u'[%s]上报的产品[%s]已生成合同,不允许再次生成' % (price_row.purchase_user.name, price_row.purchase_detail.product.name))
+                    PurchaseOrderDetail.objects.create(
+                        main=order,
+                        quality_request_text=price_row.purchase_detail.quality_request_text,
+                        count=price_row.purchase_detail.purchase_count,
+                        price=price_row.price,
+                        amount=price_row.purchase_detail.purchase_count * price_row.price,
+                        product=price_row.purchase_detail.product
+                    )
+
+                order.no = order.order_no
+                order.plan = purchase_plan
+                order.save()
+
+                order.updateAmount()
+
+                if purchase_plan:
+                    purchase_plan.updateCompact()
+
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'生成合同失败')
+    return JSONResponse({})
+
+@csrf_exempt
+@token_required
+def purchase_order_detail(request):
+    id = request.GET.get('id')
+    source = request.GET.get('source')
+    purchase_order = PurchaseOrder.getById(id)
+    user = purchase_order.create_user
+    company = purchase_order.department.getCompany()
+    rows = PurchaseOrderDetail.objects.filter(main_id=id)
+    create_time = purchase_order.create_time
+    deliver_time = purchase_order.deliver_time
+
+    if source:
+
+        status_text = PurchaseOrder.STATUS_CHOICES[purchase_order.status][1]
+        arrval_text = PurchaseOrder.ARRVAL_CHOICES[purchase_order.arrval][1]
+
+        entries_list = []
+        Godownentrys = GodownEntry.objects.filter(purchase_order=purchase_order)
+        entries_list.extend([s[0] for s in Godownentrys.values_list('no')])
+        entries_text = ','.join(entries_list)
+        main_data = {
+            'order_no': purchase_order.order_no,
+            'no': purchase_order.no,
+            'notes': purchase_order.notes,
+            'supplier_text': purchase_order.supplier and purchase_order.supplier.name or '',
+            'payment_type': purchase_order.payment_type,
+            'count': Formater.formatCountShow(purchase_order.count),
+            'amount': Formater.formatAmountShow(purchase_order.amount),
+            'apply_amount': Formater.formatAmountShow(purchase_order.apply_amount),
+            'status': purchase_order.status,
+            'status_text': status_text,
+            'arrval_text': arrval_text,
+            'entries_text': entries_text,
+            'check_user': purchase_order.check_user and purchase_order.check_user.name or '',
+            'check_time': Formater.formatStrTime(purchase_order.check_time),
+            'create_user': purchase_order.create_user and purchase_order.create_user.name or '',
+            'create_time': Formater.formatStrTime(purchase_order.create_time),
+            'deliver_time': Formater.formatStrTime(purchase_order.deliver_time),
+            'check_user2': purchase_order.check_user2 and purchase_order.check_user2.name or '',
+            'check_time2': Formater.formatStrTime(purchase_order.check_time2),
+            'check_user3': purchase_order.check_user3 and purchase_order.check_user3.name or '',
+            'check_time3': Formater.formatStrTime(purchase_order.check_time3),
+            'consignee_name': purchase_order.consignee_name,
+            'consignee_tel': purchase_order.consignee_tel,
+        }
+        data = {
+            'main_data': main_data,
+            'items_data': []
+        }
+    else:
+        data = {
+            'company': company.name,
+            'tel': user.tel,
+            'create_time': Formater.formatStrDate(create_time),
+            'deliver_time': deliver_time,
+            'items_data': []
+        }
+    for row in rows:
+        g_rows = GodownEntryDetail.objects.filter(main__purchase_order_id=id, main__status=settings.PASS, product_base_id=row.product_id)
+        arrval_date = ''
+        arrval_nos = ''
+        if g_rows:
+            arrval_nos = ','.join([s[0] for s in g_rows.values_list('main__no')])
+            arrval_date = ','.join([utils.strfdate(s[0]) for s in g_rows.values_list('main__check_time')])
+
+        item = {
+            'id': row.id,
+            'product_id': row.product_id,
+            'name': row.product.name,
+            'model': row.product.model,
+            'unit': row.product.unit,
+            'standard': row.product.standard,
+            'type_text': row.product.get_type_display(),
+            'options_type_text': row.product.option_type.name,
+            'type': row.product.type,
+            'options_type': row.product.option_type_id,
+            'quality_request_text': row.quality_request_text or '',
+            'warehouse_place': row.product.warehouse_place,
+            'count': Formater.formatCountShow(row.count),
+            'arrval_count': Formater.formatCountShow(row.arrval_count),
+            'price': Formater.formatPriceShow(row.price),
+            'amount': Formater.formatAmountShowWithTwoDecimalPlaces(row.amount),
+            'arrval_date': arrval_date,
+            'arrval_nos': arrval_nos,
+            'invoice_no': row.invoice_no or '',
+            'avg_cost_price': Formater.formatPriceShow(row.product.avg_cost_price),
+            'max_price': Formater.formatPriceShow(row.product.max_price),
+            'min_price': Formater.formatPriceShow(row.product.min_price),
+        }
+        data['items_data'].append(item)
+    return JSONResponse(data)
+
+
+@csrf_exempt
+@token_required
+def select_purchase_order(request):
+    no = request.GET.get('no')
+    if not no:
+        return JSONError(u'请输入合同单号!')
+
+    rows = PurchaseOrderDetail.objects.filter(main__no=no, main__status=PurchaseOrder.TAKE_EFFECT)
+    if rows.count() == 0:
+        return JSONError(u'合同单号不正确或该合同尚未生效!')
+    if rows[0].main.apply_amount >= rows[0].main.amount:
+        return JSONError(u'该合同已付款完毕!')
+    result = {
+          'order_id': rows[0].main.id,
+          'apply_amount': Formater.formatAmountShow(rows[0].main.apply_amount),
+          'amount': Formater.formatAmountShow(rows[0].main.amount),
+          'data': []
+    }
+    for row in rows:
+        item = {
+            'id': row.id,
+            'name': row.product.name,
+            'model': row.product.model,
+            'order_no': row.main.no,
+            'supplier_name': row.main.supplier.name,
+            'count': Formater.formatCountShow(row.count),
+            'amount': Formater.formatAmountShow(row.amount),
+        }
+        result['data'].append(item)
+    return JSONResponse(result)
+
+
+@csrf_exempt
+@permission_required('purchase.delete_purchase_order')
+def purchase_order_delete(request):
+    id = int(request.GET.get('id'))
+    try:
+        with transaction.atomic():
+            order = PurchaseOrder.getById(id)
+            if order.status != PurchaseOrder.DRAFT:
+                raise CustomError(u'该采购合同已生效, 不允许删除')
+
+            # 删除发票图片
+            image_rows = PurchaseInvoiceImage.objects.filter(order_detail__main_id=order.id)
+            images_files = []
+            for image_row in image_rows:
+                images_files.append({'url': image_row.invoice_image})
+
+            image_rows.delete()
+
+            BizLog.objects.addnew(request.user, BizLog.DELETE, u"删除采购合同[%s],id=%d" % (order.no, order.id))
+            purchase = order.plan
+            PurchaseOrderDetail.objects.filter(main=order).delete()
+            order.delete()
+
+            if purchase:
+                purchase.updateCompact()
+
+            try:
+                for item in images_files:
+                    os.remove("%s/%s" % (settings.MEDIA_ROOT, item['url']))
+            except:
+                pass
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except ProtectedError:
+        return JSONError(u'该采购合同已被引用,禁止删除!')
+    except IntegrityError:
+        return JSONError(u'该采购合同已被引用,禁止删除!')
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'删除失败!')
+
+    return JSONResponse({})
+
+@csrf_exempt
+@permission_required('purchase.check_purchase_order')
+def purchase_order_check(request):
+    id = request.GET.get('id')
+    c_type = request.GET.get('c_type')
+    status = int(request.GET.get('status'))
+    try:
+        with transaction.atomic():
+            order = PurchaseOrder.getById(id)
+            if c_type == 'check':
+                if status == PurchaseOrder.TAKE_EFFECT:
+                    if order.status == PurchaseOrder.CHECKING:
+                        raise CustomError(u'该采购合同已审核')
+                    if not order.no:
+                        raise CustomError(u'该采购合同未填写合同号, 不允许审核')
+                    exist_rows = PurchaseOrder.objects.filter(Q(no=order.no), ~Q(id=id)).first()
+                    if exist_rows:
+                        raise CustomError(u'该采购合同单号已存在')
+                    order.status = PurchaseOrder.CHECKING
+                    order.check_user = request.user
+                    order.check_time = timezone.now()
+                    BizLog.objects.addnew(
+                        request.user,
+                        BizLog.CHECK,
+                        u"审核采购合同[%s],id=%d" % (order.no, order.id),
+                    )
+                else:
+                    if order.status == PurchaseOrder.DRAFT:
+                        raise CustomError(u'该采购合同尚未审核')
+                    if order.check_user2:
+                        raise CustomError(u'该采购合同已复核')
+                    g_row = GodownEntry.objects.filter(purchase_order=order).first()
+                    if g_row:
+                        raise CustomError(u'该合同已存在入库单, 不允许撤销审核')
+                    pay = PurchasePayment.objects.filter(order=order).first()
+                    if pay:
+                        raise CustomError(u'该合同已存在付款单, 不允许撤销审核')
+
+                    d_row = PurchaseOrderDetail.objects.filter(main=order, invoice_no__isnull=False, check_status=settings.PASS).first()
+                    if d_row:
+                        raise CustomError(u'该合同已审核发票, 不允许撤销审核')
+                    order.status = PurchaseOrder.DRAFT
+                    order.check_user = None
+                    order.check_time = None
+                    BizLog.objects.addnew(
+                        request.user,
+                        BizLog.CHECK,
+                        u"采购合同取消审核[%s],id=%d" % (order.no, order.id),
+                    )
+            elif c_type == 'check2':
+                #复核
+                if status == PurchaseOrder.TAKE_EFFECT:
+                    if order.status == PurchaseOrder.DRAFT:
+                        raise CustomError(u'该采购合同未审核')
+                    if order.check_user3:
+                        raise CustomError(u'该采购合同已批准')
+
+                    order.check_user2 = request.user
+                    order.check_time2 = timezone.now()
+                    BizLog.objects.addnew(
+                        request.user,
+                        BizLog.CHECK,
+                        u"采购合同复核[%s],id=%d" % (order.no, order.id),
+                    )
+                else:
+                    if not order.check_user2:
+                        raise CustomError(u'该采购计划未复核')
+                    if order.check_user3:
+                        raise CustomError(u'该采购计划已批准')
+
+                    order.check_user2 = None
+                    order.check_time2 = None
+                    BizLog.objects.addnew(
+                        request.user,
+                        BizLog.CHECK,
+                        u"采购合同取消复核[%s],id=%d" % (order.no, order.id),
+                    )
+            elif c_type == 'check3':
+                #批准
+                if status == PurchaseOrder.TAKE_EFFECT:
+                    if not order.check_user2:
+                        raise CustomError(u'该采购合同未复核')
+                    if order.check_user3:
+                        raise CustomError(u'该采购合同已批准')
+                    order.status = status
+                    order.check_user3 = request.user
+                    order.check_time3 = timezone.now()
+                    BizLog.objects.addnew(
+                        request.user,
+                        BizLog.CHECK,
+                         u"采购合同批准[%s],id=%d" % (order.no, order.id),
+                    )
+                else:
+                    if not order.check_user3:
+                        raise CustomError(u'该采购合同未批准')
+                    g_row = GodownEntry.objects.filter(purchase_order=order).first()
+                    if g_row:
+                        raise CustomError(u'该合同已存在入库单, 不允许撤销')
+                    pay = PurchasePayment.objects.filter(order=order).first()
+                    if pay:
+                        raise CustomError(u'该合同已存在付款单, 不允许撤销')
+
+                    d_row = PurchaseOrderDetail.objects.filter(main=order, invoice_no__isnull=False,
+                                                               check_status=settings.PASS).first()
+                    if d_row:
+                        raise CustomError(u'该合同已审核发票, 不允许撤销')
+                    order.status = PurchaseOrder.CHECKING
+                    order.check_user3 = None
+                    order.check_time3 = None
+                    BizLog.objects.addnew(
+                        request.user,
+                        BizLog.CHECK,
+                         u"采购合同取消批准[%s],id=%d" % (order.no, order.id),
+                    )
+            order.save()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'审核失败')
+    return JSONResponse({})
+
+@csrf_exempt
+@permission_required('purchase.export_purchase_order')
+def purchase_order_export_detail(request):
+    id = request.GET.get('id')
+    serializer = PurchaseOrderDetailSerializer(PurchaseOrderDetail.objects.filter(main_id=id), many=True)
+    export_data = ExportChange.dict_to_obj(serializer)
+    export_data = PurchaseOrderDetailResource().export(export_data)
+    filename = utils.attachment_save(export_data)
+    BizLog.objects.addnew(request.user, BizLog.EXPORT, u"导出采购合同明细")
+    return JSONResponse({'filename': filename})
+
+@csrf_exempt
+@permission_required('purchase.view_purchase_payment')
+def purchase_payment_list(request):
+    department_ids = request.user.getSubDepartmentIds()
+    user_ids = request.user.getSubEmployeeIds()
+    rows = PurchasePayment.objects.filter(Q(create_user_id__in=user_ids) | Q(department_id__in=department_ids) | Q(create_user=request.user))
+    f = PurchasePaymentFilter(request.GET, queryset=rows)
+    total_row = f.qs.aggregate(sum_amount=Sum('amount'),sum_apply_amount=Sum('apply_amount'),sum_actual_amount=Sum('actual_amount'))
+    more = {
+        'sum_amount': Formater.formatAmountShowWithTwoDecimalPlaces(total_row['sum_amount']),
+        'sum_apply_amount': Formater.formatAmountShowWithTwoDecimalPlaces(total_row['sum_apply_amount']),
+        'sum_actual_amount': Formater.formatAmountShowWithTwoDecimalPlaces(total_row['sum_actual_amount'])
+    }
+
+    rows, total = utils.get_page_data(request, f.qs)
+    serializer = PurchasePaymentSerializer(rows, many=True)
+    return DataGridJSONResponse(serializer.data, total, more)
+
+
+@csrf_exempt
+@permission_required('purchase.export_purchase_payment')
+def purchase_payment_export(request):
+    department_ids = request.user.getSubDepartmentIds()
+    user_ids = request.user.getSubEmployeeIds()
+    rows = PurchasePayment.objects.filter(Q(create_user_id__in=user_ids) | Q(department_id__in=department_ids) | Q(create_user=request.user))
+    f = PurchasePaymentFilter(request.GET, queryset=rows)
+    serializer = PurchasePaymentSerializer(f.qs, many=True)
+    export_data = ExportChange.dict_to_obj(serializer)
+    export_data = PurchasePaymentResource().export(export_data)
+    filename = utils.attachment_save(export_data)
+    BizLog.objects.addnew(request.user, BizLog.EXPORT, u"导出付款管理")
+    return JSONResponse({'filename': filename})
+
+@csrf_exempt
+@permission_required('purchase.add_purchase_payment')
+def purchase_payment_save(request):
+    id = request.GET.get('id')
+    data = json.loads(request.body)
+
+    try:
+        with transaction.atomic():
+            apply_amount = Formater.formatAmount(data['apply_amount'])
+            if apply_amount <= 0:
+                raise CustomError(u'本次申请金额必须大于0')
+            total_amount = apply_amount
+            amount = Formater.formatAmount(data['amount'])
+            sum_rows = PurchasePayment.objects.filter(order_id=data['order_id']).aggregate(sum_amount=Sum('apply_amount'))
+            if sum_rows:
+                total_amount += sum_rows['sum_amount'] or 0
+
+            # if total_amount > amount:
+            #     raise CustomError(u'该合同申请金额大于合同总金额')
+
+            order_data = {'order': data['order_id'], 'amount': amount, 'apply_amount': apply_amount, 'notes': data['notes']}
+            pb = PurchasePaymentSerializer.factory(request.user, order_data, id)
+            if pb.instance and pb.instance.status == settings.PASS:
+                raise CustomError(u'该付款单已审核')
+            # 更新原合同单的申请金额
+            if pb.instance:
+                order = pb.instance.order
+                order.apply_amount -= pb.instance.apply_amount
+                order.save()
+            pb = pb.validSave()
+            pb.order.updateApplyAmount() #更新合同单的申请金额
+
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'保存失败')
+    return JSONResponse({})
+
+@csrf_exempt
+@token_required
+def purchase_payment_detail(request):
+    id = request.GET.get('id')
+    company = PurchasePayment.getById(id).department.getCompany()
+    payment = PurchasePayment.getById(id)
+    rows = PurchaseOrderDetail.objects.filter(main_id=payment.order_id)
+    order_id = None
+    if rows:
+        order_id = rows[0].main_id
+
+    result = {'company': company.name, 'order_id': order_id, 'data': []}
+    for row in rows:
+        item = {
+            'id': row.id,
+            'product_id': row.product_id,
+            'purchase_detail_id': row.id,
+            'supplier_name': row.main.supplier.name,
+            'supplier_account': row.main.supplier.account,
+            'name': row.product.name,
+            'model': row.product.model,
+            'order_no': row.main.no,
+            'amount': Formater.formatAmountShowWithTwoDecimalPlaces(row.amount),
+            'count': Formater.formatCountShow(row.count),
+            'price': Formater.formatPriceShow(row.price),
+            'avg_cost_price': Formater.formatPriceShow(row.product.avg_cost_price),
+            'unit': row.product.unit or '',
+            'standard': row.product.standard or '',
+            'quality_request_text': row.quality_request_text or '',
+            'max_price': Formater.formatPriceShow(row.product.max_price),
+            'min_price': Formater.formatPriceShow(row.product.min_price),
+        }
+        result['data'].append(item)
+    return JSONResponse(result)
+
+
+@csrf_exempt
+@permission_required('purchase.delete_purchase_payment')
+def purchase_payment_delete(request):
+    id = int(request.GET.get('id'))
+    try:
+        with transaction.atomic():
+            payment = PurchasePayment.getById(id)
+            if payment.status != settings.DEFAULT:
+                raise CustomError(u'该付款单已审核, 不允许删除')
+
+            #更新合同单的申请金额
+            payment.order.apply_amount -= payment.apply_amount
+            payment.order.save()
+
+            BizLog.objects.addnew(request.user, BizLog.DELETE, u"删除采购付款单[%s],id=%d" % (payment.no, payment.id))
+            PurchasePaymentDetail.objects.filter(main=payment).delete()
+            payment.delete()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except ProtectedError:
+        return JSONError(u'该付款单已被引用,禁止删除!')
+    except IntegrityError:
+        return JSONError(u'该付款单已被引用,禁止删除!')
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'删除失败!')
+
+    return JSONResponse({})
+
+@csrf_exempt
+@permission_required('purchase.check_purchase_payment')
+def purchase_payment_check(request):
+    id = request.GET.get('id')
+    status = int(request.GET.get('status'))
+    try:
+        with transaction.atomic():
+            order = PurchasePayment.getById(id)
+            if status == settings.PASS:
+                if order.status == settings.PASS:
+                    raise CustomError(u'该付款单已审核')
+                order.status = settings.CHECKING
+                order.check_user = request.user
+                order.check_time = timezone.now()
+                BizLog.objects.addnew(
+                    request.user,
+                    BizLog.CHECK,
+                    u"审核采购付款[%s],id=%d" % (order.no, order.id),
+                )
+            else:
+                if order.status == settings.DEFAULT:
+                    raise CustomError(u'该付款单尚未审核')
+                if order.review_time:
+                    raise CustomError(u'该付款单已复核,请先撤销复核')
+                if order.is_pay:
+                    raise CustomError(u'该付款单已付款,禁止撤销审核')
+                order.status = settings.DEFAULT
+                order.check_user = None
+                order.check_time = None
+                BizLog.objects.addnew(
+                    request.user,
+                    BizLog.CHECK,
+                    u"采购付款单取消审核[%s],id=%d" % (order.no, order.id),
+                )
+            order.save()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'审核失败')
+    return JSONResponse({})
+
+@csrf_exempt
+@permission_required('purchase.review_purchase_payment')
+def purchase_payment_review(request):
+    id = request.GET.get('id')
+    status = int(request.GET.get('status'))
+    try:
+        with transaction.atomic():
+            order = PurchasePayment.getById(id)
+            if status == 1:
+                if order.status == settings.DEFAULT:
+                    raise CustomError(u'该付款单尚未审核')
+                if order.review_time:
+                    raise CustomError(u'该付款单已复核')
+                order.review_user = request.user
+                order.review_time = timezone.now()
+                BizLog.objects.addnew(
+                    request.user,
+                    BizLog.CHECK,
+                    u"复核采购付款[%s],id=%d" % (order.no, order.id),
+                )
+            else:
+                if not order.review_time:
+                    raise CustomError(u'该付款单尚未复核')
+                if order.ratify_time:
+                    raise CustomError(u'该付款单已批准')
+                if order.is_pay:
+                    raise CustomError(u'该付款单已付款,禁止撤销复核')
+                order.review_user = None
+                order.review_time = None
+                BizLog.objects.addnew(
+                    request.user,
+                    BizLog.CHECK,
+                    u"采购付款单撤销复核[%s],id=%d" % (order.no, order.id),
+                )
+            order.save()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'复核失败')
+    return JSONResponse({})
+
+
+@csrf_exempt
+@permission_required('purchase.ratify_purchase_payment')
+def purchase_payment_ratify(request):
+    id = request.GET.get('id')
+    status = int(request.GET.get('status'))
+    try:
+        with transaction.atomic():
+            order = PurchasePayment.getById(id)
+            if status == 1:
+                if order.status == settings.DEFAULT:
+                    raise CustomError(u'该付款单尚未审核')
+                if not order.review_time:
+                    raise CustomError(u'该付款单尚未复核,请先复核')
+                if order.ratify_time:
+                    raise CustomError(u'该付款单已批准')
+                order.status = settings.PASS
+                order.ratify_user = request.user
+                order.ratify_time = timezone.now()
+                BizLog.objects.addnew(
+                    request.user,
+                    BizLog.CHECK,
+                    u"批准采购付款[%s],id=%d" % (order.no, order.id),
+                )
+            else:
+                if not order.ratify_time:
+                    raise CustomError(u'该付款单尚未批准')
+                if order.is_pay:
+                    raise CustomError(u'该付款单已付款,禁止撤销复核')
+                order.status = settings.CHECKING
+                order.ratify_user = None
+                order.ratify_time = None
+                BizLog.objects.addnew(
+                    request.user,
+                    BizLog.CHECK,
+                    u"采购付款单撤销批准[%s],id=%d" % (order.no, order.id),
+                )
+            order.save()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'批准失败')
+    return JSONResponse({})
+
+@csrf_exempt
+@permission_required('purchase.export_purchase_payment')
+def purchase_payment_export_detail(request):
+    id = request.GET.get('id')
+    payment = PurchasePayment.getById(id)
+    rows = PurchaseOrderDetail.objects.filter(main_id=payment.order_id)
+    export_data = PurchasePaymentDetailResource().export(rows)
+    filename = utils.attachment_save(export_data)
+    BizLog.objects.addnew(request.user, BizLog.EXPORT, u"导出付款管理明细")
+    return JSONResponse({'filename': filename})
+
+
+@csrf_exempt
+@permission_required('purchase.pay_purchase_payment')
+def purchase_payment_pay(request):
+    id = request.GET.get('id')
+    data = json.loads(request.body)
+    try:
+        with transaction.atomic():
+            payment = PurchasePayment.getById(id)
+
+            if payment.apply_amount <= payment.actual_amount:
+                raise CustomError(u'付款单已付款完毕')
+
+            if not payment.ratify_time:
+                raise CustomError(u'付款单未批准,禁止付款')
+            if not payment.review_time:
+                raise CustomError(u'付款单未复核,禁止付款')
+            try:
+                data['actual_amount'] = Formater.formatAmount(data['actual_amount'])
+            except:
+                raise CustomError(u'付款金额无效')
+
+            if data['actual_amount'] <= 0:
+                raise CustomError(u'付款金额应该大于零')
+            data['main'] = payment.id
+            pb = PurchasePaySerializer.factory(request.user, data)
+            pb = pb.validSave()
+            payment.updateActualAmount()
+            payment.is_pay = True
+            payment.save()
+            BizLog.objects.addnew(
+                request.user,
+                BizLog.UPDATE,
+                u"采购付款单[%s]付款,id=%d" % (payment.no, payment.id),
+            )
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'保存失败')
+    return JSONResponse({})
+
+@csrf_exempt
+@token_required
+def purchase_payment_pay_detail(request):
+    id = request.GET.get('id')
+    try:
+        instances = PurchasePay.getByMainId(id)
+        data = []
+        for instance in instances:
+            item = {
+                'id': instance.id,
+                'payment_type': instance.payment_type.name,
+                'payment_time': Formater.formatStrTime(instance.payment_time),
+                'actual_amount': Formater.formatAmountShowWithTwoDecimalPlaces(instance.actual_amount),
+                'payment_user_text': instance.payment_user.name,
+                'notes': instance.notes or ''
+            }
+            data.append(item)
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'没有付款记录')
+    return JSONResponse(data)
+
+@csrf_exempt
+@permission_required('purchase.view_purchase_invoice')
+def purchase_invoice_list(request):
+    invoice = request.GET.get('invoice')
+    rows = PurchaseOrderDetail.objects.filter(main__status=PurchaseOrder.TAKE_EFFECT)
+    department_ids = request.user.getSubDepartmentIds()
+    user_ids = request.user.getSubEmployeeIds()
+    rows = rows.filter(Q(main__create_user_id__in=user_ids) | Q(main__department_id__in=department_ids) | Q(main__create_user=request.user))
+    if invoice == '0':
+        rows = rows.filter(invoice_no__isnull=True)
+    elif invoice == '1':
+        rows = rows.filter(invoice_no__isnull=False)
+    f = PurchaseOrderDetailFilter(request.GET, queryset=rows)
+
+    total_row = f.qs.aggregate(sum_invoice_amount=Sum('invoice_amount'), sum_amount=Sum('amount'))
+    more = {
+        'sum_amount': Formater.formatAmountShow(total_row['sum_amount']),
+        'sum_invoice_amount': Formater.formatAmountShow(total_row['sum_invoice_amount'])
+    }
+
+    rows, total = utils.get_page_data(request, f.qs)
+    serializer = PurchaseOrderDetailSerializer(rows, many=True)
+    return DataGridJSONResponse(serializer.data, total, more)
+
+
+@csrf_exempt
+@permission_required('purchase.export_purchase_invoice')
+def purchase_invoice_export(request):
+    invoice = request.GET.get('invoice')
+    rows = PurchaseOrderDetail.objects.filter(main__status=PurchaseOrder.TAKE_EFFECT)
+    department_ids = request.user.getSubDepartmentIds()
+    user_ids = request.user.getSubEmployeeIds()
+    rows = rows.filter(Q(main__create_user_id__in=user_ids) | Q(main__department_id__in=department_ids) | Q(
+        main__create_user=request.user))
+    if invoice == '0':
+        rows = rows.filter(invoice_no__isnull=True)
+    elif invoice == '1':
+        rows = rows.filter(invoice_no__isnull=False)
+
+    f = PurchaseOrderDetailFilter(request.GET, queryset=rows)
+    serializer = PurchaseOrderDetailSerializer(f.qs, many=True)
+    export_data = ExportChange.dict_to_obj(serializer)
+    export_data = PurchaseInvoiceResource().export(export_data)
+    filename = utils.attachment_save(export_data)
+    BizLog.objects.addnew(request.user, BizLog.EXPORT, u"导出发票管理")
+    return JSONResponse({'filename': filename})
+
+@csrf_exempt
+@permission_required('purchase.add_purchase_invoice')
+def purchase_invoice_save(request):
+    data = json.loads(request.body)
+
+    try:
+        with transaction.atomic():
+            try:
+                amount = Formater.formatAmount(data['invoice_amount'])
+            except:
+                raise CustomError(u'发票金额无效')
+
+            items = data['items']
+            for item in items:
+                order = PurchaseOrderDetail.getById(item['id'])
+                if order.check_status == settings.PASS:
+                    raise CustomError(u'合同[%s]中产品[%s]发票已审核' % (order.main.no, order.product.name))
+
+                order.invoice_no = data['invoice_no']
+                order.invoice_amount = amount
+                order.invoice_date = data['invoice_date']
+                order.create_user = request.user
+                order.department = request.user.department
+                order.create_time = timezone.now()
+                order.save()
+            BizLog.objects.addnew(
+                request.user,
+                BizLog.INSERT,
+                u"录入发票[%s]" % data['invoice_no'],
+                data
+            )
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'保存失败')
+    return JSONResponse({})
+
+@csrf_exempt
+@permission_required('purchase.check_purchase_invoice')
+def purchase_invoice_check(request):
+    id = request.GET.get('id')
+    status = int(request.GET.get('status'))
+    try:
+        with transaction.atomic():
+            order = PurchaseOrderDetail.getById(id)
+            if status == settings.PASS:
+                if order.check_status == settings.PASS:
+                    raise CustomError(u'该单据已审核')
+                if not order.invoice_no or order.invoice_no == '':
+                    raise CustomError(u'该单据尚未录入发票')
+                order.check_status = settings.PASS
+                order.check_user = request.user
+                order.check_time = timezone.now()
+                BizLog.objects.addnew(
+                    request.user,
+                    BizLog.CHECK,
+                    u"审核采购发票[%s],id=%d" % (order.main.no, order.id),
+                )
+            else:
+                if order.check_status == settings.DEFAULT:
+                    raise CustomError(u'该单据尚未审核')
+                order.check_status = settings.DEFAULT
+                order.check_user = None
+                order.check_time = None
+                BizLog.objects.addnew(
+                    request.user,
+                    BizLog.CHECK,
+                    u"采购发票取消审核[%s],id=%d" % (order.main.no, order.id),
+                )
+            order.save()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'审核失败')
+    return JSONResponse({})
+
+
+@csrf_exempt
+@permission_required('purchase.add_purchase_invoice')
+def purchase_invoice_upload_image(request):
+    ids = request.POST.get('ids')
+    files = request.FILES.getlist('file')
+    try:
+        with transaction.atomic():
+            ids = str(ids).split(',')
+            from models import path_and_rename
+            for id in ids:
+                order = PurchaseOrderDetail.objects.filter(id=id).first()
+                if not order:
+                    raise CustomError(u'未找到合同')
+                if order.check_status == settings.PASS:
+                    raise CustomError(u'该合同已生效')
+                for file in files:
+                    filename = "%s%d-%s.%s" % (
+                        path_and_rename.path,
+                        order.id,
+                        timezone.now().strftime('%Y%m%d%H%M%S%f'),
+                        file.name.split('.')[-1]
+                    )
+                    filename = filename.lower()
+                    full_filename = "%s/%s" % (settings.MEDIA_ROOT, filename)
+                    with open(full_filename, 'wb+') as destination:
+                        for chunk in file.chunks():
+                            destination.write(chunk)
+                    #utils.resize_image(full_filename, 800)
+
+                    PurchaseInvoiceImage.objects.create(order_detail=order, invoice_image=filename)
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception,e:
+        traceback.print_exc()
+        return JSONError(u'上传失败')
+    return JSONResponse({})
+
+@csrf_exempt
+@token_required
+def purchase_invoice_images(request):
+    id = request.GET.get('id')
+    rows = PurchaseInvoiceImage.objects.filter(order_detail_id=int(id))
+    rows, total = utils.get_page_data(request, rows)
+    data = []
+    for row in rows:
+        item = {
+            'id': row.id,
+            'check_status': row.order_detail.check_status,
+            'order_detail_id': row.order_detail.id,
+            'image': unicode(row.invoice_image),
+            'image_name': str(row.invoice_image).split('/')[-1],
+            'image_url': row.invoice_image.url,
+        }
+        data.append(item)
+    return DataGridJSONResponse(data, total)
+
+@csrf_exempt
+@permission_required('purchase.add_purchase_invoice')
+def purchase_invoice_del_image(request):
+    id = request.GET.get('id')
+
+    try:
+        with transaction.atomic():
+            img = PurchaseInvoiceImage.objects.filter(id=int(id)).first()
+            if img.order_detail.check_status == settings.PASS:
+                raise CustomError(u'该发票已审核, 不允许删除')
+            img.delete()
+            try:
+                os.remove("%s/%s" % (settings.MEDIA_ROOT, img.invoice_image))
+            except:
+                pass
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except:
+        traceback.print_exc()
+        return JSONError(u"删除失败")
+    return JSONResponse({})
+
+
+
+@csrf_exempt
+@token_required
+def search_product(request):
+    param = request.GET.get('param')
+    type = request.GET.get('type')
+    warehouse_id = request.GET.get('warehouse')
+    rows = ProductBase.objects.filter(enabled=True, type__lt=ProductBase.GOODS)
+
+    if type:
+        rows = rows.filter(type=int(type))
+
+    if param:
+        rows = rows.filter(Q(name__icontains=param) | Q(model__icontains=param) | Q(code__icontains=param))
+
+    rows, total = utils.get_page_data(request, rows)
+
+    data = []
+    for row in rows:
+        record_data = []
+        if warehouse_id:
+            record_data = GetWarehouseSrockRecord.getRecord(row.id, warehouse_id)
+
+        item = {
+            'id': row.id,
+            'name': row.name,
+            'model': row.model,
+            'unit': row.unit,
+            'standard': row.standard,
+            'warehouse_place': row.warehouse_place,
+            'type_text': row.get_type_display(),
+            'option_type_text': row.option_type and row.option_type.name or '',
+            'record_data': record_data
+        }
+        data.append(item)
+
+    return JSONResponse({'data': data, 'total': total})
+
+@csrf_exempt
+@token_required
+def search_user(request):
+    id = request.GET.get('id')
+    purchase = PurchasePlan.getById(id)
+    purchase_users = PurchaseUser.objects.filter(purchase=purchase)
+    rows = User.objects.filter()
+    data = []
+    for row in rows:
+        item = {
+            'id': row.id,
+            'name': row.name,
+            'department': row.department.name,
+            'check': 0,
+            'is_report': 0,
+            'name_dept': u"{}--{}".format(row.name,row.department.name),
+        }
+        for purchase_user in purchase_users:
+            if purchase_user.purchase_user.id == row.id:
+                item['check'] = 1
+                is_report = PurchasePrice.objects.filter(purchase_user_id=row.id,purchase_detail__purchase=purchase,report=True).count()
+                if is_report:
+                    item['is_report'] = 1
+                break
+        data.append(item)
+    return JSONResponse(data)
+
+
+@csrf_exempt
+@token_required
+def search_price(request):
+    rows = PurchasePrice.objects.filter(Q(supplier__isnull=False) & Q(purchase_user=request.user) & Q(report=False))
+    data = []
+    for row in rows:
+        item = {
+            'id': row.id,
+            'name': row.purchase_detail.product.name,
+            'model': row.purchase_detail.product.model,
+            'price': Formater.formatPriceShow(row.price),
+            'supplier': row.supplier.name,
+            'notes': row.notes or '',
+        }
+        data.append(item)
+    return JSONResponse(data)
+
+@token_required
+def godownentry_list(request):
+    type = int(request.GET.get('type'))
+    product_notes = request.GET.get('product_notes')
+    try:
+        valid_permission(request.user,GodownEntry.getPermissionByType(type, 'view'))
+    except:
+        return DataGridJSONResponse([], 0)
+
+    warehouses_ids = Warehouse.getManagerWarehouses(request.user)
+    department_ids = request.user.getSubDepartmentIds()
+    user_ids = request.user.getSubEmployeeIds()
+    rows = GodownEntry.objects.filter(product_type=type,warehouse_id__in=warehouses_ids)
+    rows = rows.filter(Q(department_id__in=department_ids) | Q(create_user_id__in=user_ids) | Q(create_user=request.user))
+    if product_notes:
+        g_ids = rows.values_list('id')
+        d_ids = GodownEntryDetail.objects.filter(main_id__in=g_ids, notes__icontains=product_notes).values_list('main_id')
+        rows = rows.filter(id__in=d_ids)
+
+    f = GodownEntryFilter(request.GET, queryset=rows)
+    total_row = f.qs.aggregate(total_count=Sum('total_count'), total_amount=Sum('total_amount'))
+    more = {
+        'total_count': Formater.formatCountShow(total_row['total_count']),
+        'total_amount': Formater.formatAmountShow(total_row['total_amount'])
+    }
+    rows, total = utils.get_page_data(request, f.qs)
+    serializer = GodownEntrySerializer(rows, many=True)
+    return DataGridJSONResponse(serializer.data, total, more)
+
+
+@csrf_exempt
+@token_required
+def godownentry_save(request):
+    id = request.GET.get('id')
+    main_data = json.loads(request.POST.get('main'))
+    items_data = json.loads(request.POST.get('item'))
+    try:
+        type = GodownEntry.getValidType(request.GET.get('type'))
+        main_data['product_type'] = type
+        with transaction.atomic():
+            serializer = GodownEntrySerializer.factory(request.user, main_data, id)
+            if serializer.instance and serializer.instance.status == settings.PASS:
+                raise CustomError(u'该入库单已审核,禁止修改!')
+            serializer = serializer.validSave()
+            valid_permission(request.user, serializer.getPermission('add'))
+            GodownEntryDetail.objects.filter(main=serializer).delete()
+            for item in items_data:
+                item['main'] = serializer.id
+                detail_serializer = GodownEntryDetailSerializer.factory(request.user, data=item)
+                detail_serializer.validSave()
+            serializer.update_total()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'保存失败!')
+    return JSONResponse()
+
+
+
+@token_required
+def godownentry_delete(request):
+    id = request.GET.get('id')
+    try:
+        with transaction.atomic():
+            instance = GodownEntry.getById(id)
+            valid_permission(request.user, instance.getPermission('delete'))
+            if instance.status == settings.PASS:
+                raise CustomError(u'该入库单已审核,禁止删除!')
+
+            BizLog.objects.addnew(request.user, BizLog.DELETE, u"删除入库单[%s],id=%d" % (instance.no, instance.id))
+            GodownEntryDetail.objects.filter(main=instance).delete()
+            instance.delete()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except ProtectedError:
+        return JSONError(u'该入库单已被引用,禁止删除!')
+    except IntegrityError:
+        return JSONError(u'该入库单已被引用,禁止删除!')
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'删除失败!')
+
+    return JSONResponse({})
+
+
+
+@token_required
+def godownentry_detail(request):
+    id = request.GET.get('id')
+    instance = GodownEntry.getById(id)
+    company = instance.department.getCompany()
+    if instance.status == settings.PASS:
+        status_text = u'已审核'
+    else:
+        status_text = u'待审核'
+    main_data = {
+        'id': instance.id,
+        'warehouse_id': instance.warehouse_id,
+        'warehouse_name': instance.warehouse.name,
+        'supplier_id': instance.supplier_id,
+        'supplier_name': instance.supplier.name,
+        'create_user_text': instance.create_user.name,
+        'status': instance.status,
+        'status_text': status_text,
+        'check_user_text': instance.check_user and instance.check_user.name or ' ',
+        'check_time': Formater.formatStrTime(instance.check_time),
+        'total_count': Formater.formatCountShow(instance.total_count),
+        'total_amount': Formater.formatAmountShow(instance.total_amount),
+        'create_time': Formater.formatStrTime(instance.create_time),
+        'notes': instance.notes or '',
+        'no': instance.no,
+        'company': company.name,
+        'purchase_order_no':instance.purchase_order and instance.purchase_order.no or ''
+    }
+
+    data = {
+        'main_data': main_data,
+        'items_data': []
+    }
+    detail_rows = GodownEntryDetail.objects.filter(main=instance)
+    for detail_row in detail_rows:
+        item_data = {
+            'id': detail_row.id,
+            'product_base_id': detail_row.product_base_id,
+            'product_base_name': detail_row.product_base.name,
+            'product_base_model': detail_row.product_base.model,
+            'price': Formater.formatPriceShow(detail_row.price),
+            'count': Formater.formatCountShow(detail_row.count),
+            'amount': Formater.formatAmountShow(detail_row.amount),
+            'invoice_amount': Formater.formatAmountShow(detail_row.invoice_amount),
+            'warehouse_place': detail_row.product_base.warehouse_place or '',
+            'unit':detail_row.product_base.unit or '',
+            'notes':detail_row.notes or ''
+        }
+        data['items_data'].append(item_data)
+
+    return JSONResponse(data)
+
+
+@token_required
+def godownentry_export(request):
+    try:
+        type = GodownEntry.getValidType(request.GET.get('type'))
+        warehouses_ids = Warehouse.getManagerWarehouses(request.user)
+        department_ids = request.user.getSubDepartmentIds()
+        user_ids = request.user.getSubEmployeeIds()
+        rows = GodownEntry.objects.filter(product_type=type, warehouse_id__in=warehouses_ids)
+        rows = rows.filter(
+            Q(department_id__in=department_ids) | Q(create_user_id__in=user_ids) | Q(create_user=request.user))
+        f = GodownEntryFilter(request.GET, queryset=rows)
+        serializer = GodownEntrySerializer(f.qs, many=True)
+        valid_permission(request.user, GodownEntry.getPermissionByType(type, 'export'))
+        export_data = ExportChange.dict_to_obj(serializer)
+        if type == GodownEntry.MATERIAL:
+            perm = 'material.view_material_cost'
+        elif type == GodownEntry.CONSUMABLE:
+            perm = 'material.view_consumable_cost'
+        is_show_cost = isHasPermissions(request.user, perm)
+        export_data = GodownEntryResource(is_show_cost).export(export_data)
+        filename = utils.attachment_save(export_data)
+        BizLog.objects.addnew(request.user, BizLog.EXPORT, u"导出入库单" )
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'导出列表失败!')
+
+    return JSONResponse({'filename': filename})
+
+
+@token_required
+def godownentry_export_detail(request):
+    id = request.GET.get('id')
+    instance = GodownEntry.getById(id)
+    try:
+        valid_permission(request.user, instance.getPermission('export'))
+        godown_entry_detail = GodownEntryDetail.objects.filter(main=instance)
+        serializer = GodownEntryDetailSerializer(godown_entry_detail, many=True)
+        export_data = ExportChange.dict_to_obj(serializer)
+        if instance.product_type == GodownEntry.MATERIAL:
+            perm = 'material.view_material_cost'
+        elif instance.product_type == GodownEntry.CONSUMABLE:
+            perm = 'material.view_consumable_cost'
+        is_show_cost = isHasPermissions(request.user, perm)
+        export_data = GodownEntryDetailResource(is_show_cost).export(export_data)
+        filename = utils.attachment_save(export_data)
+        BizLog.objects.addnew(request.user, BizLog.EXPORT, u"导出入库单[%s]明细,id=%d" % (instance.no, instance.id))
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'导出明细失败!')
+    return JSONResponse({'filename': filename})
+
+
+@csrf_exempt
+@token_required
+def godownentry_import(request):
+    file = request.FILES.get('excel_file')
+    main_data = json.loads(request.POST.get('main_data'))
+
+    try:
+        type = GodownEntry.getValidType(request.GET.get('type'))
+        main_data['product_type'] = type
+        valid_permission(request.user, GodownEntry.getPermissionByType(type, 'import'))
+        line = 2
+        importer = GodownEntryImporter()
+        excel_rows = importer.getExcelData(file)
+        with transaction.atomic():
+
+            serializer = GodownEntrySerializer.factory(request.user, main_data)
+            serializer = serializer.validSave()
+            for excel_row in excel_rows:
+                try:
+                    row = importer.validRow(excel_row)
+                    model = row[u'产品代码']
+                    product_base = ProductBase.objects.filter(model=model, type=type)
+                    if product_base.count() == 0:
+                        raise CustomError(u'产品代码不存在')
+                    elif product_base.count() > 1:
+                        raise CustomError(u'产品代码重复,前往基础数据设置修改')
+                    else:
+                        product_base = product_base.first()
+                    items_data = {}
+                    items_data['product_base'] = product_base.id
+                    items_data['main'] = serializer.id
+                    items_data['price'] = row[u'单价']
+                    items_data['count'] = row[u'数量']
+                    items_data['invoice_amount'] = row[u'发票金额']
+                    items_data['notes'] = row[u'备注']
+
+                    detail_serializer = GodownEntryDetailSerializer.factory(request.user, items_data)
+                    detail_serializer.validSave()
+                except CustomError, e:
+                    raise CustomError(u'第%d行:%s' % (line, e.get_error_msg()))
+                except Exception, e:
+                    raise CustomError(u'第%d行:%s' % (line, unicode(e)))
+                line += 1
+            serializer.update_total()
+            BizLog.objects.addnew(request.user, BizLog.IMPORT, u"导入入库单[%s],id=%d" % (serializer.no, serializer.id))
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'导入失败!')
+    return JSONResponse()
+
+@csrf_exempt
+@token_required
+def godownentry_senior_edit(request):
+    def updateStock(product_base, warehouse):
+        sum_row = WarehouseRecord.objects.filter(
+            warehouse=warehouse,
+            product=product_base
+        ).aggregate(
+            count=Sum('count'),
+            amount=Sum('amount'),
+            amount2=Sum('amount2')
+        )
+        warehouse_stock = WarehouseStock.objects.filter(product=product_base, warehouse=warehouse).first()
+        if warehouse_stock:
+            warehouse_stock.count = sum_row['count'] or 0
+            warehouse_stock.amount = sum_row['amount'] or 0
+            warehouse_stock.amount2 = sum_row['amount2'] or 0
+            if warehouse_stock.count != 0:
+                warehouse_stock.avg_cost_price = warehouse_stock.amount / warehouse_stock.count
+                warehouse_stock.avg_cost_price2 = warehouse_stock.amount2 / warehouse_stock.count
+            warehouse_stock.save()
+
+        sum_row = WarehouseStock.objects.filter(
+            product=product_base
+        ).aggregate(
+            count=Sum('count'),
+            amount=Sum('amount'),
+            amount2=Sum('amount2')
+        )
+
+        product_base.stock_count = sum_row['count'] or 0
+        product_base.stock_amount = sum_row['amount'] or 0
+        product_base.stock_amount2 = sum_row['amount2'] or 0
+        if product_base.stock_count > 0:
+            product_base.avg_cost_price = product_base.stock_amount / product_base.stock_count
+            product_base.avg_cost_price2 = product_base.stock_amount2 / product_base.stock_count
+        product_base.save()
+
+
+    def updateWarehouseRecord(warehouse_record):
+        sum_row = WarehouseRecordDetail.objects.filter(
+            warehouse_record=warehouse_record
+        ).aggregate(
+            sum_amount=Sum(F('count') * F('warehouse_stock_record__entry_price')),
+            sum_amount2=Sum(F('count') * F('warehouse_stock_record__entry_price2')),
+            sum_count=Sum('count')
+        )
+        warehouse_record.amount = sum_row['sum_amount'] or 0
+        warehouse_record.amount2 = sum_row['sum_amount2'] or 0
+        warehouse_record.count = sum_row['sum_count'] or 0
+        warehouse_record.save()
+
+    def changeEntryPrice(entry_detail,entry_price,entry_price2):
+        changeEntryPriceBase(entry_detail, entry_price, entry_price2)
+        rows = WarehouseRecordDetail.objects.filter(warehouse_stock_record=entry_detail.stock_record)
+
+        ck_type = (WarehouseRecord.CK_ZJ, WarehouseRecord.CK_XS,)
+        pk_type = (WarehouseRecord.CK_PK,)
+        tl_type = (WarehouseRecord.TL,)
+        th_type = (WarehouseRecord.TH,)
+        for row in rows:
+            warehouse_record = row.warehouse_record
+            updateWarehouseRecord(warehouse_record)
+            if warehouse_record.type in ck_type:
+                deliver_detail = DeliverDetail.objects.filter(warehouse_record=warehouse_record).first()
+                if deliver_detail:
+                    #更新出库明细的合计成本
+                    deliver_detail.total_cost = warehouse_record.amount
+                    deliver_detail.save()
+
+                    # 更新出库单的合计成本
+                    order = deliver_detail.main
+                    sum_row = DeliverDetail.objects.filter(
+                        main=order
+                    ).aggregate(
+                        sum_cost=Sum('total_cost')
+                    )
+                    order.total_cost = sum_row['sum_cost'] or 0
+                    order.save()
+            elif warehouse_record.type in tl_type:
+                detail = DeliverReturnDetail.objects.filter(warehouse_record=warehouse_record).first()
+                if detail:
+                    #更新退料明细的合计成本
+                    old_amount = detail.return_cost
+                    detail.return_cost = warehouse_record.amount
+                    detail.save()
+
+                    # 更新出库明细的退料合计成本
+                    new_amount = detail.return_cost
+                    detail.deliver_detail.return_cost += new_amount - old_amount
+                    detail.deliver_detail.save()
+
+                    #更新出库单的退料合计成本
+                    detail.deliver_detail.main.return_cost += new_amount - old_amount
+                    detail.deliver_detail.main.save()
+
+                    #更新退料单的合计成本
+                    order = detail.main
+                    sum_row = DeliverReturnDetail.objects.filter(
+                        main=order
+                    ).aggregate(
+                        sum_cost=Sum('return_cost')
+                    )
+                    order.return_cost = sum_row['sum_cost'] or 0
+                    order.save()
+
+            elif warehouse_record.type in th_type:
+                detail = GodownEntryReturnDetail.objects.filter(warehouse_record=warehouse_record).first()
+                if detail:
+                    old_amount = detail.amount
+                    #更新退货明细的成本
+                    detail.amount = warehouse_record.amount
+                    detail.price = 0
+                    if detail.count:
+                        detail.price = detail.amount / detail.count
+                    detail.save()
+
+                    #更新入库明细的退货成本
+                    if detail.godownentry_detail:
+                        new_amount = detail.amount
+                        detail.godownentry_detail.return_amount += new_amount - old_amount
+                        detail.godownentry_detail.save()
+
+                        #更新入库单的退货成本
+                        detail.godownentry_detail.main.return_amount += new_amount - old_amount
+                        detail.godownentry_detail.main.save()
+
+                    #更新退货单的合计成本
+                    order = detail.main
+                    sum_row = GodownEntryReturnDetail.objects.filter(
+                        main=order
+                    ).aggregate(
+                        sum_cost=Sum('amount')
+                    )
+                    order.total_amount = sum_row['sum_cost'] or 0
+                    order.save()
+            elif warehouse_record.type in pk_type:
+                detail = InventoryDetail.objects.filter(warehouse_record=warehouse_record).first()
+                if detail:
+                    #更新盘存明细
+                    detail.amount = warehouse_record.amount
+                    detail.price = 0
+                    if detail.count:
+                        detail.price = detail.amount / detail.count
+                    detail.save()
+
+                    # 更新盘存单
+                    order = detail.main
+                    sum_row = InventoryDetail.objects.filter(
+                        main=order
+                    ).aggregate(
+                        sum_amount=Sum('amount')
+                    )
+                    order.total_amount = sum_row['sum_amount'] or 0
+                    order.save()
+
+
+    def changeEntryPriceBase(entry_detail,entry_price,entry_price2):
+        entry_detail.price = entry_price
+        entry_detail.amount = entry_detail.count * entry_price
+        entry_detail.save()
+
+        if entry_detail.stock_record:
+            entry_detail.stock_record.entry_price = entry_price
+            entry_detail.stock_record.entry_price2 = entry_price2
+            entry_detail.stock_record.save()
+
+    def changeSupplier(godownentry, new_supplier_id):
+        new_supplier = Supplier.objects.filter(id=new_supplier_id).first()
+        if not new_supplier:
+            raise CustomError(u'未找到相应的供应商')
+        godownentry.supplier = new_supplier
+        godownentry.save()
+        rows = GodownEntryDetail.objects.filter(main=godownentry)
+        for row in rows:
+            if not row.stock_record:
+                continue
+            row.stock_record.supplier = new_supplier
+            row.stock_record.save()
+
+    def changePrice(godownentry_detail,new_entry_price):
+        changeEntryPrice(godownentry_detail, new_entry_price, new_entry_price)
+        updateStock(godownentry_detail.product_base, godownentry_detail.main.warehouse)
+
+    def addCount(godownentry_detail,add_count):
+        godownentry_detail.count += add_count
+        godownentry_detail.amount = godownentry_detail.count * godownentry_detail.price
+        godownentry_detail.save()
+        if godownentry_detail.stock_record:
+            godownentry_detail.stock_record.entry_count += add_count
+            godownentry_detail.stock_record.surplus_count += add_count
+            godownentry_detail.stock_record.save()
+
+            record_detail = WarehouseRecordDetail.objects.filter(
+                warehouse_stock_record=godownentry_detail.stock_record,
+                warehouse_record__type__in=(WarehouseRecord.RK_CG, WarehouseRecord.RK_ZJ)
+            ).first()
+            if record_detail:
+                record_detail.count += add_count
+                record_detail.save()
+                updateWarehouseRecord(record_detail.warehouse_record)
+        updateStock(godownentry_detail.product_base, godownentry_detail.main.warehouse)
+
+    def decCount(godownentry_detail,red_count):
+        godownentry_detail.count -= red_count
+        godownentry_detail.amount = godownentry_detail.count * godownentry_detail.price
+        godownentry_detail.save()
+        if godownentry_detail.stock_record:
+            if red_count > godownentry_detail.stock_record.surplus_count:
+                raise CustomError(u'该入库单中的配件[%s],剩余%s,不能减少%s,请使用退货减少库存' % (
+                    godownentry_detail.product_base.name,
+                    Formater.formatCountShow(godownentry_detail.stock_record.surplus_count),
+                    Formater.formatCountShow(red_count)
+                ))
+            godownentry_detail.stock_record.entry_count -= red_count
+            godownentry_detail.stock_record.surplus_count -= red_count
+            godownentry_detail.stock_record.save()
+
+            record_detail = WarehouseRecordDetail.objects.filter(
+                warehouse_stock_record=godownentry_detail.stock_record,
+                warehouse_record__type__in=(WarehouseRecord.RK_CG, WarehouseRecord.RK_ZJ)
+            ).first()
+            if record_detail:
+                record_detail.count -= red_count
+                record_detail.save()
+                updateWarehouseRecord(record_detail.warehouse_record)
+
+        updateStock(godownentry_detail.product_base, godownentry_detail.main.warehouse)
+
+    main_data = json.loads(request.POST.get('main'))
+    new_rows = json.loads(request.POST.get('item'))
+    id = request.GET.get('id')
+    new_supplier_id = main_data['supplier']
+
+    try:
+        new_supplier_id = int(new_supplier_id)
+    except:
+        return JSONError(u"无效的供应商!")
+
+
+    try:
+        with transaction.atomic():
+            godownentry = GodownEntry.objects.filter(pk=int(id)).first()
+            if not godownentry:
+                raise CustomError(u'未找到相应的入库单')
+            valid_permission(request.user, godownentry.getPermission('edit'))
+            if godownentry.status != settings.PASS:
+                raise CustomError(u'未通过审核的入库单不允许高级修改')
+
+            if new_supplier_id != godownentry.supplier_id:
+                changeSupplier(godownentry,new_supplier_id)
+
+            for row in new_rows:
+                new_entry_count = Formater.formatCount(row['new_count'])
+                new_entry_price = Formater.formatPrice(row['new_price'])
+                if new_entry_count < 0:
+                    raise CustomError(u'入库数量不能小于0')
+                if new_entry_price < 0:
+                    raise CustomError(u'入库价不能小于0')
+
+                detail = GodownEntryDetail.objects.filter(id=int(row['new_detail_id'])).first()
+                if not detail:
+                    continue
+                if detail.price !=  new_entry_price:
+                    changePrice(detail,new_entry_price)
+                if detail.count < new_entry_count:
+                    addCount(detail,new_entry_count-detail.count)
+                if detail.count > new_entry_count:
+                    decCount(detail, detail.count-new_entry_count)
+
+                WarehouseRecord.updateCurStockByProduct(detail.main.warehouse_id,detail.product_base_id)
+
+            count = 0
+            amount = 0
+            sum_row = GodownEntryDetail.objects.filter(main=godownentry).aggregate(
+                count_sum=Sum('count'),
+                amount_sum=Sum('amount')
+            )
+            if sum_row:
+                count = sum_row['count_sum'] or 0
+                amount = sum_row['amount_sum'] or 0
+            godownentry.total_count = count
+            godownentry.total_amount = amount
+            godownentry.save()
+
+            BizLog.objects.addnew(
+                request.user,
+                BizLog.UPDATE,
+                u"原料高级修改[单号=%s],id=%d" % (godownentry.no, godownentry.id)
+            )
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'修改失败')
+    return JSONResponse({})
+
+
+@token_required
+def godownentry_check(request):
+    id = request.GET.get('id')
+    try:
+        with transaction.atomic():
+            instance = GodownEntry.getById(id)
+            valid_permission(request.user, instance.getPermission('check'))
+            if instance.status == settings.PASS:
+                raise CustomError(u'该入库单已审核')
+            godownentry_details = GodownEntryDetail.objects.filter(main=instance)
+            for godownentry_detail in godownentry_details:
+                if instance.purchase_order:
+                    type = WarehouseRecord.RK_CG
+                else:
+                    type = WarehouseRecord.RK_ZJ
+
+                stock_record = BizWarehouse.entry(type,
+                                                  godownentry_detail.product_base,
+                                                  instance.warehouse,
+                                                  instance.supplier,
+                                                  godownentry_detail.count,
+                                                  godownentry_detail.price,
+                                                  godownentry_detail.price)
+                godownentry_detail.stock_record = stock_record
+                godownentry_detail.save()
+
+                # 更新产品入库最高价和最低价
+                product = ProductBase.objects.filter(id=godownentry_detail.product_base_id).first()
+                if product:
+                    if product.max_price < godownentry_detail.price:
+                        product.max_price = godownentry_detail.price
+                    if product.min_price > godownentry_detail.price:
+                        product.min_price = godownentry_detail.price
+                    if not product.min_price:
+                        product.min_price = godownentry_detail.price
+                    product.save()
+
+            instance.status = settings.PASS
+            instance.check_user = request.user
+            instance.check_time = timezone.now()
+            BizLog.objects.addnew(
+                request.user,
+                BizLog.CHECK,
+                u"审核入库[%s],id=%d" % (instance.no, instance.id),
+            )
+
+            instance.save()
+            if instance.purchase_order:
+                instance.purchase_order.updateArrval()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'审核失败')
+    return JSONResponse({})
+
+@token_required
+def godownentry_query_list(request): # 原料耗材入库查询
+    try:
+        valid_permission(request.user, getPermissionByType(int(request.GET.get('type')), 'view'))
+    except:
+        return DataGridJSONResponse([], 0)
+    rows = get_filter_data(request)
+    total_row = rows.aggregate(godownentry_total_count=Sum('godown_entry_detail_ref_stock_record__count'),
+                               inventory_total_count=Sum('inventory_details_ref_warehouse_stock_record__count'),
+                               godownentry_total_return_count = Sum('godown_entry_detail_ref_stock_record__return_count'),
+                               inventory_total_return_count = Sum('inventory_details_ref_warehouse_stock_record__return_count'),
+                               godownentry_total_amount=Sum('godown_entry_detail_ref_stock_record__amount'),
+                               inventory_total_amount=Sum('inventory_details_ref_warehouse_stock_record__amount'),
+                               godownentry_total_deliver_count=Sum('godown_entry_detail_ref_stock_record__deliver_count'),
+                               inventory_total_deliver_count=Sum('inventory_details_ref_warehouse_stock_record__deliver_count'),
+                               total_surplus_count=Sum('surplus_count')
+                               )
+    more = {
+        'total_count': Formater.formatCountShow((total_row['godownentry_total_count'] or 0) + (total_row['inventory_total_count'] or 0)),
+        'total_return_count': Formater.formatCountShow((total_row['godownentry_total_return_count'] or 0) + (total_row['inventory_total_return_count'] or 0)),
+        'total_amount': Formater.formatAmountShow((total_row['godownentry_total_amount'] or 0) + (total_row['inventory_total_amount'] or 0)),
+        'total_deliver_count': Formater.formatCountShow((total_row['godownentry_total_deliver_count'] or 0) + (total_row['inventory_total_deliver_count'] or 0)),
+        'total_surplus_count':Formater.formatCountShow(total_row['total_surplus_count'] or 0)
+    }
+
+    rows, total = utils.get_page_data(request, rows)
+    data = get_godownentry_query_data(rows)
+    return DataGridJSONResponse(data, total, more)
+
+
+@token_required
+def godownentry_query_export(request):
+    type = int(request.GET.get('type'))
+    try:
+        valid_permission(request.user, getPermissionByType(type, 'export'))
+        rows = get_filter_data(request)
+        data = get_godownentry_query_data(rows)
+        export_data = ExportChange.dict_to_obj2(data)
+        if type == GodownEntry.MATERIAL:
+            perm = 'material.view_material_cost'
+        elif type == GodownEntry.CONSUMABLE:
+            perm = 'material.view_consumable_cost'
+        is_show_cost = isHasPermissions(request.user, perm)
+        export_data = GodownEntryQueryResource(is_show_cost).export(export_data)
+        filename = utils.attachment_save(export_data)
+        BizLog.objects.addnew(request.user, BizLog.EXPORT, u"导出入库查询")
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'导出入库查询失败!')
+    return JSONResponse({'filename': filename})
+
+
+@token_required
+def godownentry_query_detail(request):
+    rows = get_filter_data(request)
+    data = get_godownentry_query_data(rows)
+    return JSONResponse(data)
+
+
+def get_filter_data(request):
+    product_type = int(request.GET.get('type'))
+    check_time = request.GET.get('check_time')
+    no = request.GET.get('no')
+    supplier = request.GET.get('supplier')
+    create_user = request.GET.get('create_user')
+    product_text = request.GET.get('product_text')
+    product_model = request.GET.get('product_model')
+    warehouse = request.GET.get('warehouse')
+    entry_type = request.GET.get('entry_type')
+    notes = request.GET.get('notes')
+    rows = WarehouseStockRecord.objects.filter(product__type=product_type,
+                                               warehouse_record_detail_ref_stock_record__warehouse_record__type__in=[0,
+                                                                                                                     1,
+                                                                                                                     2]).order_by(
+        '-id')
+    if entry_type:
+        rows = rows.filter(warehouse_record_detail_ref_stock_record__warehouse_record__type=int(entry_type))
+    if product_text:
+        rows = rows.filter(product__name__icontains=product_text)
+    if product_model:
+        rows = rows.filter(product__model__icontains=product_model)
+    if warehouse:
+        rows = rows.filter(warehouse__name__icontains=warehouse)
+    if check_time:
+        check_time_begin = check_time.split(' - ')[0]
+        check_time_end = check_time.split(' - ')[1] + ' 23:59:59'
+        rows = rows.filter(Q(Q(godown_entry_detail_ref_stock_record__main__check_time__gt=check_time_begin) &
+                             Q(godown_entry_detail_ref_stock_record__main__check_time__lt=check_time_end)) |
+                           Q(Q(inventory_details_ref_warehouse_stock_record__main__check_time__gt=check_time_begin) &
+                             Q(inventory_details_ref_warehouse_stock_record__main__check_time__lt=check_time_end)))
+    if no:
+        rows = rows.filter(Q(godown_entry_detail_ref_stock_record__main__no__icontains=no) |
+                           Q(inventory_details_ref_warehouse_stock_record__main__no__icontains=no))
+    if notes:
+        rows = rows.filter(Q(godown_entry_detail_ref_stock_record__notes__icontains=notes) |
+                           Q(inventory_details_ref_warehouse_stock_record__notes__icontains=notes))
+    if supplier:
+        rows = rows.filter(godown_entry_detail_ref_stock_record__main__supplier__name__icontains=supplier)
+    if create_user:
+        rows = rows.filter(
+            Q(godown_entry_detail_ref_stock_record__main__create_user__name__icontains=create_user) |
+            Q(inventory_details_ref_warehouse_stock_record__main__create_user__name__icontains=create_user))
+
+    warehouses_ids = Warehouse.getManagerWarehouses(request.user)
+    department_ids = request.user.getSubDepartmentIds()
+    user_ids = request.user.getSubEmployeeIds()
+    rows = rows.filter(warehouse_id__in=warehouses_ids)
+    rows = rows.filter(Q(Q(inventory_details_ref_warehouse_stock_record__main__department_id__in=department_ids)
+                       | Q(inventory_details_ref_warehouse_stock_record__main__create_user_id__in=user_ids)
+                       | Q(inventory_details_ref_warehouse_stock_record__main__create_user=request.user))
+                       | Q(Q(godown_entry_detail_ref_stock_record__main__department_id__in=department_ids)
+                        | Q(godown_entry_detail_ref_stock_record__main__create_user_id__in=user_ids)
+                        | Q(godown_entry_detail_ref_stock_record__main__create_user=request.user)
+    ))
+
+    return rows
+
+
+def get_godownentry_query_data(rows):
+
+    rows = rows.values(
+        'id',
+        'godown_entry_detail_ref_stock_record__main__supplier__name',
+        'godown_entry_detail_ref_stock_record__product_base__name',
+        'godown_entry_detail_ref_stock_record__product_base__model',
+        'godown_entry_detail_ref_stock_record__product_base__unit',
+        'godown_entry_detail_ref_stock_record__product_base__type',
+        'godown_entry_detail_ref_stock_record__product_base__warehouse_place',
+
+        'inventory_details_ref_warehouse_stock_record__product__name',
+        'inventory_details_ref_warehouse_stock_record__product__model',
+        'inventory_details_ref_warehouse_stock_record__product__unit',
+        'inventory_details_ref_warehouse_stock_record__product__type',
+        'inventory_details_ref_warehouse_stock_record__product__warehouse_place',
+
+        'warehouse__name',
+        'warehouse_record_detail_ref_stock_record__warehouse_record__type',
+
+        'godown_entry_detail_ref_stock_record__id',
+        'godown_entry_detail_ref_stock_record__price',
+        'godown_entry_detail_ref_stock_record__count',
+        'godown_entry_detail_ref_stock_record__return_count',
+        'godown_entry_detail_ref_stock_record__amount',
+        'godown_entry_detail_ref_stock_record__notes',
+        'godown_entry_detail_ref_stock_record__deliver_count',
+        'godown_entry_detail_ref_stock_record__main__create_user__name',
+        'godown_entry_detail_ref_stock_record__main__check_time',
+        'godown_entry_detail_ref_stock_record__main__no',
+
+        'inventory_details_ref_warehouse_stock_record__price',
+        'inventory_details_ref_warehouse_stock_record__count',
+        'inventory_details_ref_warehouse_stock_record__return_count',
+        'inventory_details_ref_warehouse_stock_record__amount',
+        'inventory_details_ref_warehouse_stock_record__notes',
+        'inventory_details_ref_warehouse_stock_record__deliver_count',
+        'inventory_details_ref_warehouse_stock_record__main__create_user__name',
+        'inventory_details_ref_warehouse_stock_record__main__check_time',
+        'inventory_details_ref_warehouse_stock_record__main__no',
+    )
+
+    data = []
+    for row in rows:
+        warehouse_record_type = WarehouseRecord.TYPE_CHOICES[row['warehouse_record_detail_ref_stock_record__warehouse_record__type']][1]
+        if row['warehouse_record_detail_ref_stock_record__warehouse_record__type'] == WarehouseRecord.RK_PY:
+            product_type_text = ProductBase.TYPE_CHOICES[row['inventory_details_ref_warehouse_stock_record__product__type']][1]
+            surplus_count = row['inventory_details_ref_warehouse_stock_record__count'] - row['inventory_details_ref_warehouse_stock_record__return_count'] - row['inventory_details_ref_warehouse_stock_record__deliver_count']
+            item = {
+                'id': row['id'],
+                'type': warehouse_record_type,
+                'product_type': product_type_text,
+                'supplier': '',
+                'product_name': row['inventory_details_ref_warehouse_stock_record__product__name'],
+                'product_model': row['inventory_details_ref_warehouse_stock_record__product__model'],
+                'product_unit': row['inventory_details_ref_warehouse_stock_record__product__unit'],
+                'warehouse_place': row['inventory_details_ref_warehouse_stock_record__product__warehouse_place'],
+                'warehouse': row['warehouse__name'],
+                'price': Formater.formatPriceShow(row['inventory_details_ref_warehouse_stock_record__price']),
+                'count': Formater.formatCountShow(row['inventory_details_ref_warehouse_stock_record__count']),
+                'return_count': Formater.formatCountShow(row['inventory_details_ref_warehouse_stock_record__return_count']),
+                'amount': Formater.formatAmountShow(row['inventory_details_ref_warehouse_stock_record__amount']),
+                'deliver_count': Formater.formatCountShow(row['inventory_details_ref_warehouse_stock_record__deliver_count']),
+                'surplus_count': Formater.formatCountShow(surplus_count),
+                'create_user': row['inventory_details_ref_warehouse_stock_record__main__create_user__name'],
+                'check_time': Formater.formatStrTime(row['inventory_details_ref_warehouse_stock_record__main__check_time']),
+                'notes': row['inventory_details_ref_warehouse_stock_record__notes'],
+                'no': row['inventory_details_ref_warehouse_stock_record__main__no'],
+                'is_PY': True # 是否是盘盈单
+            }
+        else:
+            product_type_text = ProductBase.TYPE_CHOICES[row['godown_entry_detail_ref_stock_record__product_base__type']][1]
+            surplus_count = row['godown_entry_detail_ref_stock_record__count'] - row['godown_entry_detail_ref_stock_record__return_count'] - row['godown_entry_detail_ref_stock_record__deliver_count']
+            item = {
+                'id': row['id'],
+                'godownentry_detail_id': row['godown_entry_detail_ref_stock_record__id'],
+                'type': warehouse_record_type,
+                'product_type': product_type_text,
+                'supplier': row['godown_entry_detail_ref_stock_record__main__supplier__name'],
+                'product_name': row['godown_entry_detail_ref_stock_record__product_base__name'],
+                'product_model': row['godown_entry_detail_ref_stock_record__product_base__model'],
+                'product_unit': row['godown_entry_detail_ref_stock_record__product_base__unit'],
+                'warehouse_place': row['godown_entry_detail_ref_stock_record__product_base__warehouse_place'],
+                'warehouse': row['warehouse__name'],
+                'price': Formater.formatPriceShow(row['godown_entry_detail_ref_stock_record__price']),
+                'count': Formater.formatCountShow(row['godown_entry_detail_ref_stock_record__count']),
+                'return_count': Formater.formatCountShow(row['godown_entry_detail_ref_stock_record__return_count']),
+                'amount': Formater.formatAmountShow(row['godown_entry_detail_ref_stock_record__amount']),
+                'deliver_count': Formater.formatCountShow(row['godown_entry_detail_ref_stock_record__deliver_count']),
+                'surplus_count': Formater.formatCountShow(surplus_count),
+                'create_user': row['godown_entry_detail_ref_stock_record__main__create_user__name'],
+                'check_time': Formater.formatStrTime(row['godown_entry_detail_ref_stock_record__main__check_time']),
+                'notes': row['godown_entry_detail_ref_stock_record__notes'],
+                'no': row['godown_entry_detail_ref_stock_record__main__no'],
+                'is_PY': False
+            }
+        data.append(item)
+    return data
+
+def getPermissionByType(type, action):
+    permissions = {
+        ProductBase.MATERIAL: {'view': 'purchase.view_material_godownentry_query',
+                               'export': 'purchase.export_material_godownentry_query',
+                               },
+        ProductBase.CONSUMABLE: {'view': 'purchase.view_consumable_godownentry_query',
+                                 'export': 'purchase.export_consumable_godownentry_query',
+                               }
+    }
+    return permissions[type][action]
+
+
+@token_required
+def godownentry_return_list(request):
+    type = int(request.GET.get('type'))
+    product_notes = request.GET.get('product_notes')
+    try:
+        valid_permission(request.user, GodownEntryReturn.getPermissionByType(type, 'view'))
+    except:
+        return DataGridJSONResponse([], 0)
+    warehouses_ids = Warehouse.getManagerWarehouses(request.user)
+    department_ids = request.user.getSubDepartmentIds()
+    user_ids = request.user.getSubEmployeeIds()
+    rows = GodownEntryReturn.objects.filter(type=type, warehouse_id__in=warehouses_ids)
+    rows = rows.filter(
+        Q(department_id__in=department_ids) | Q(create_user_id__in=user_ids) | Q(create_user=request.user))
+    if product_notes:
+        g_ids = rows.values_list('id')
+        d_ids = GodownEntryReturnDetail.objects.filter(main_id__in=g_ids, notes__icontains=product_notes).values_list('main_id')
+        rows = rows.filter(id__in=d_ids)
+    f = GodownEntryReturnFilter(request.GET, queryset=rows)
+    total_row = f.qs.aggregate(total_count=Sum('total_count'), total_amount=Sum('total_amount'))
+    more = {
+        'total_count': Formater.formatCountShow(total_row['total_count']),
+        'total_amount': Formater.formatAmountShow(total_row['total_amount'])
+    }
+    rows, total = utils.get_page_data(request, f.qs)
+    serializer = GodownEntryReturnSerializer(rows, many=True)
+    return DataGridJSONResponse(serializer.data, total, more)
+
+
+@csrf_exempt
+@token_required
+def godownentry_return_save(request):
+    id = request.GET.get('id')
+    main_data = json.loads(request.POST.get('main'))
+    items_data = json.loads(request.POST.get('item'))
+    try:
+        type = GodownEntryReturn.getValidType(request.GET.get('type'))
+        main_data['type'] = type
+        with transaction.atomic():
+            serializer = GodownEntryReturnSerializer.factory(request.user, main_data, id)
+            if serializer.instance and serializer.instance.status == settings.PASS:
+                raise CustomError(u'该退货单已审核,禁止修改!')
+            valid_permission(request.user, GodownEntryReturn.getPermissionByType(type, 'add'))
+            serializer = serializer.validSave()
+            GodownEntryReturnDetail.objects.filter(main=serializer).delete()
+            for item in items_data:
+                item['main'] = serializer.id
+                item['amount'] = item['total_cost']
+                detail_serializer = GodownEntryReturnDetailSerializer.factory(request.user, item)
+                detail_serializer.validSave()
+            serializer.update_total()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'保存失败!')
+    return JSONResponse()
+
+
+@token_required
+def godownentry_return_detail(request):
+    id = request.GET.get('id')
+    instance = GodownEntryReturn.getById(id)
+    company = instance.department.getCompany()
+    warehouse_id = instance.warehouse_id
+    supplier_id = instance.supplier and instance.supplier_id or None
+    if instance.status == settings.PASS:
+        status_text = u'已审核'
+    else:
+        status_text = u'待审核'
+    main_data = {
+        'id': instance.id,
+        'warehouse_id': warehouse_id,
+        'warehouse_name': instance.warehouse.name,
+        'supplier_id': supplier_id,
+        'supplier_name': instance.supplier and instance.supplier.name or '',
+        'create_user_name': instance.create_user.name,
+        'create_time': Formater.formatStrTime(instance.create_time),
+        'status': instance.status,
+        'status_text': status_text,
+        'check_user_text': instance.check_user and instance.check_user.name or ' ',
+        'check_time': Formater.formatStrTime(instance.create_time),
+        'total_count': Formater.formatCountShow(instance.total_count),
+        'total_amount': Formater.formatAmountShow(instance.total_amount),
+        'notes': instance.notes or '',
+        'no': instance.no,
+        'company': company.name
+    }
+
+    data = {
+        'main_data': main_data,
+        'items_data': []
+    }
+    detail_rows = GodownEntryReturnDetail.objects.filter(main=instance)
+    for detail_row in detail_rows:
+
+        is_godown = False
+        godownentry_detail = None
+        if detail_row.godownentry_detail:
+            is_godown = True
+            godownentry_detail = detail_row.godownentry_detail_id
+        record_data = GetWarehouseSrockRecord.getRecord(detail_row.product_base_id, warehouse_id, supplier_id)
+        item_data = {
+            'detail_id': detail_row.id,
+            'id': detail_row.id,
+            'product_base_id': detail_row.product_base_id,
+            'product_base_name': detail_row.product_base.name,
+            'product_base_model': detail_row.product_base.model,
+            'price': Formater.formatPriceShow(detail_row.price),
+            'count': Formater.formatCountShow(detail_row.count),
+            'amount': Formater.formatAmountShow(detail_row.amount),
+            'warehouse_place': detail_row.product_base.warehouse_place,
+            'unit':detail_row.product_base.unit or '',
+            'is_godown': is_godown,
+            'godownentry_detail': godownentry_detail,
+            'notes': detail_row.notes or '',
+            'entry_no': detail_row.godownentry_detail and detail_row.godownentry_detail.main.no or '',
+            'record_data':record_data
+        }
+        data['items_data'].append(item_data)
+
+    return JSONResponse(data)
+
+@token_required
+def get_godownentry_detail_data(request):
+    id = request.GET.get('id')
+    godownentry_detail = GodownEntryDetail.getById(id)
+    supplier_id = godownentry_detail.main.supplier_id or None
+    warehouse_id = godownentry_detail.main.warehouse_id or None
+    record_data = GetWarehouseSrockRecord.getRecord(godownentry_detail.product_base_id, warehouse_id, supplier_id)
+    data = {
+        'main': {
+            'supplier': supplier_id,
+            'supplier_text': godownentry_detail.main.supplier.name or '',
+            'warehouse': warehouse_id,
+        },
+        'items':[{
+            'product_base_id': godownentry_detail.product_base_id,
+            'product_base_name': godownentry_detail.product_base.name,
+            'product_base_model': godownentry_detail.product_base.model,
+            'unit': godownentry_detail.product_base.unit or '',
+            'count': Formater.formatCountShow(godownentry_detail.count),
+            'price': Formater.formatPriceShow(godownentry_detail.price),
+            'godownentry_detail_id': id,
+            'notes': godownentry_detail.notes or '',
+            'record_data':record_data
+        }]
+    }
+    return JSONResponse(data)
+
+@token_required
+def godownentry_return_export(request):
+    try:
+        type = GodownEntryReturn.getValidType(request.GET.get('type'))
+        warehouses_ids = Warehouse.getManagerWarehouses(request.user)
+        department_ids = request.user.getSubDepartmentIds()
+        user_ids = request.user.getSubEmployeeIds()
+        rows = GodownEntryReturn.objects.filter(type=type, warehouse_id__in=warehouses_ids)
+        rows = rows.filter(
+            Q(department_id__in=department_ids) | Q(create_user_id__in=user_ids) | Q(create_user=request.user))
+        f = GodownEntryReturnFilter(request.GET, queryset=rows)
+        serializer = GodownEntryReturnSerializer(f.qs, many=True)
+        valid_permission(request.user, GodownEntryReturn.getPermissionByType(type, 'export'))
+        export_data = ExportChange.dict_to_obj(serializer)
+        if type == GodownEntryReturn.MATERIAL:
+            perm = 'material.view_material_cost'
+        elif type == GodownEntryReturn.CONSUMABLE:
+            perm = 'material.view_consumable_cost'
+        is_show_cost = isHasPermissions(request.user, perm)
+        export_data = GodownEntryReturnResource(is_show_cost).export(export_data)
+        filename = utils.attachment_save(export_data)
+        BizLog.objects.addnew(request.user, BizLog.EXPORT, u"导出退货单" )
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'导出列表失败!')
+
+    return JSONResponse({'filename': filename})
+
+
+@token_required
+def godownentry_return_export_detail(request):
+    id = request.GET.get('id')
+    instance = GodownEntryReturn.getById(id)
+    if instance.type == GodownEntryReturn.MATERIAL:
+        perm = 'material.view_material_cost'
+    elif instance.type == GodownEntryReturn.CONSUMABLE:
+        perm = 'material.view_consumable_cost'
+    is_show_cost = isHasPermissions(request.user, perm)
+    try:
+        valid_permission(request.user, instance.getPermission('export'))
+        godown_entry_return_detail = GodownEntryReturnDetail.objects.filter(main=instance)
+        serializer = GodownEntryReturnDetailSerializer(godown_entry_return_detail, many=True)
+        export_data = ExportChange.dict_to_obj(serializer)
+        export_data = GodownEntryReturnDetailResource(is_show_cost).export(export_data)
+        filename = utils.attachment_save(export_data)
+        BizLog.objects.addnew(request.user, BizLog.EXPORT, u"导出退货单[%s]明细,id=%d" % (instance.no, instance.id))
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'导出明细失败!')
+    return JSONResponse({'filename': filename})
+
+
+@token_required
+def godownentry_return_check(request):
+    id = request.GET.get('id')
+    try:
+        with transaction.atomic():
+            instance = GodownEntryReturn.getById(id)
+            valid_permission(request.user, instance.getPermission('check'))
+            if instance.status == settings.PASS:
+                raise CustomError(u'该退货单已审核!')
+            godownentry_return_details = GodownEntryReturnDetail.objects.filter(main=instance)
+            for return_detail in godownentry_return_details:
+                if return_detail.godownentry_detail:
+                    warehouse_record = BizWarehouse.entryBack(return_detail.godownentry_detail.stock_record, return_detail.count)
+                else:
+                    warehouse_record = BizWarehouse.entryBatchBack(return_detail.product_base, return_detail.main.warehouse, return_detail.count, return_detail.main.supplier)
+                return_detail.warehouse_record = warehouse_record
+                return_detail.amount = -warehouse_record.amount
+                return_detail.price = return_detail.amount / return_detail.count
+                return_detail.save()
+
+            instance.status = settings.PASS
+            instance.check_user = request.user
+            instance.check_time = timezone.now()
+            BizLog.objects.addnew(
+                request.user,
+                BizLog.CHECK,
+                u"审核退货[%s],id=%d" % (instance.no, instance.id),
+            )
+            instance.update_total()
+            instance.save()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'审核失败')
+    return JSONResponse({})
+
+
+
+
+
+@token_required
+def godownentry_return_delete(request):
+    id = request.GET.get('id')
+    try:
+        with transaction.atomic():
+            instance = GodownEntryReturn.getById(id)
+            valid_permission(request.user, instance.getPermission('delete'))
+            if instance.status == settings.PASS:
+                raise CustomError(u'该退货单已审核,禁止删除!')
+
+            BizLog.objects.addnew(request.user, BizLog.DELETE, u"删除退货单[%s],id=%d" % (instance.no, instance.id))
+            GodownEntryReturnDetail.objects.filter(main=instance).delete()
+            instance.delete()
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except ProtectedError:
+        return JSONError(u'该退货单已被引用,禁止删除!')
+    except IntegrityError:
+        return JSONError(u'该退货单已被引用,禁止删除!')
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'删除失败!')
+
+    return JSONResponse({})
+
+
+@token_required
+def godownentry_return_query_list(request):
+    product_type = int(request.GET.get('type'))
+    try:
+        valid_permission(request.user, getReturnPermissionByType(product_type, 'view'))
+    except:
+        return DataGridJSONResponse([], 0)
+    warehouses_ids = Warehouse.getManagerWarehouses(request.user)
+    department_ids = request.user.getSubDepartmentIds()
+    user_ids = request.user.getSubEmployeeIds()
+    rows = GodownEntryReturnDetail.objects.filter(product_base__type=product_type, main__status=settings.PASS, main__warehouse_id__in=warehouses_ids)
+    rows = rows.filter(
+        Q(main__department_id__in=department_ids) | Q(main__create_user_id__in=user_ids) | Q(main__create_user=request.user))
+    f = GodownEntryReturnDetailFilter(request.GET, queryset=rows)
+    total_row = f.qs.aggregate(total_count=Sum('count'), total_amount=Sum('amount'))
+    more = {
+        'total_count': Formater.formatCountShow(total_row['total_count']),
+        'total_amount': Formater.formatAmountShow(total_row['total_amount'])
+    }
+    rows, total = utils.get_page_data(request, f.qs)
+    serializer = GodownEntryReturnDetailSerializer(rows, many=True)
+    return DataGridJSONResponse(serializer.data, total, more)
+
+@token_required
+def godownentry_return_query_export(request):
+    product_type = int(request.GET.get('type'))
+    try:
+        warehouses_ids = Warehouse.getManagerWarehouses(request.user)
+        department_ids = request.user.getSubDepartmentIds()
+        user_ids = request.user.getSubEmployeeIds()
+        rows = GodownEntryReturnDetail.objects.filter(product_base__type=product_type, main__status=settings.PASS,
+                                                      main__warehouse_id__in=warehouses_ids)
+        rows = rows.filter(
+            Q(main__department_id__in=department_ids) | Q(main__create_user_id__in=user_ids) | Q(main__create_user=request.user))
+        f = GodownEntryReturnDetailFilter(request.GET, queryset=rows)
+        serializer = GodownEntryReturnDetailSerializer(f.qs, many=True)
+        valid_permission(request.user, getReturnPermissionByType(product_type, 'export'))
+        export_data = ExportChange.dict_to_obj(serializer)
+        if product_type == ProductBase.MATERIAL:
+            perm = 'material.view_material_cost'
+        elif product_type == ProductBase.CONSUMABLE:
+            perm = 'material.view_consumable_cost'
+        is_show_cost = isHasPermissions(request.user, perm)
+        export_data = GodownEntryReturnQueryResource(is_show_cost).export(export_data)
+        filename = utils.attachment_save(export_data)
+        BizLog.objects.addnew(request.user, BizLog.EXPORT, u"导出退货查询")
+    except CustomError, e:
+        return JSONError(e.get_error_msg())
+    except Exception, e:
+        traceback.print_exc()
+        return JSONError(u'导出退货查询失败!')
+
+    return JSONResponse({'filename': filename})
+
+@token_required
+def godownentry_return_query_detail(request):
+    product_type = int(request.GET.get('type'))
+    warehouses_ids = Warehouse.getManagerWarehouses(request.user)
+    department_ids = request.user.getSubDepartmentIds()
+    user_ids = request.user.getSubEmployeeIds()
+    rows = GodownEntryReturnDetail.objects.filter(product_base__type=product_type, main__status=settings.PASS,
+                                                  main__warehouse_id__in=warehouses_ids)
+    rows = rows.filter(
+        Q(main__department_id__in=department_ids) | Q(main__create_user_id__in=user_ids) | Q(main__create_user=request.user))
+    f = GodownEntryReturnDetailFilter(request.GET, queryset=rows)
+
+    serializer = GodownEntryReturnDetailSerializer(f.qs, many=True)
+    return JSONResponse(serializer.data)
+
+def getReturnPermissionByType(type, action):
+    permissions = {
+        ProductBase.MATERIAL: {'view': 'account.view_material_godownentry_return_query',
+                               'export': 'account.export_material_godownentry_return_query',
+                               },
+        ProductBase.CONSUMABLE: {'view': 'account.view_consumable_godownentry_return_query',
+                                 'export': 'account.export_consumable_godownentry_return_query',
+                               }
+    }
+    return permissions[type][action]

部分文件因为文件数量过多而无法显示