feat: 项目文档

This commit is contained in:
Czw996 2021-12-16 17:54:31 +08:00
parent 453c9de0d8
commit 91ef89f18b
65 changed files with 489 additions and 277 deletions

View file

@ -2,5 +2,5 @@ from django.contrib import admin
from apps.data.models import * from apps.data.models import *
admin.site.register([Warehouse, Client, Supplier, Account, admin.site.register([Warehouse, ClientCategory, Client, SupplierCategory, Supplier,
ChargeItem, ClientCategory, SupplierCategory, GoodsCategory, GoodsUnit]) Account, ChargeItem])

View file

@ -1,3 +1,4 @@
from extensions.common.base import *
from extensions.models import * from extensions.models import *
@ -32,6 +33,17 @@ class Warehouse(Model):
return number return number
class ClientCategory(Model):
"""客户分类"""
name = CharField(max_length=64, verbose_name='名称')
remark = CharField(max_length=256, null=True, blank=True, verbose_name='备注')
team = ForeignKey('system.Team', on_delete=CASCADE, related_name='client_categories')
class Meta:
unique_together = [('name', 'team')]
class Client(Model): class Client(Model):
"""客户""" """客户"""
@ -75,11 +87,16 @@ class Client(Model):
return number return number
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
self.has_arrears = self.arrears_amount > 0 class SupplierCategory(Model):
if update_fields: """供应商分类"""
update_fields.append('has_arrears')
return super().save(force_insert, force_update, using, update_fields) name = CharField(max_length=64, verbose_name='名称')
remark = CharField(max_length=256, null=True, blank=True, verbose_name='备注')
team = ForeignKey('system.Team', on_delete=CASCADE, related_name='supplier_categories')
class Meta:
unique_together = [('name', 'team')]
class Supplier(Model): class Supplier(Model):
@ -118,12 +135,6 @@ class Supplier(Model):
return number return number
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
self.has_arrears = self.arrears_amount > 0
if update_fields:
update_fields.append('has_arrears')
return super().save(force_insert, force_update, using, update_fields)
class Account(Model): class Account(Model):
"""结算账户""" """结算账户"""
@ -164,12 +175,6 @@ class Account(Model):
return number return number
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
self.has_balance = self.balance_amount > 0
if update_fields:
update_fields.append('has_balance')
return super().save(force_insert, force_update, using, update_fields)
class ChargeItem(Model): class ChargeItem(Model):
"""收支项目""" """收支项目"""
@ -189,52 +194,7 @@ class ChargeItem(Model):
unique_together = [('name', 'team')] unique_together = [('name', 'team')]
class ClientCategory(Model):
"""客户分类"""
name = CharField(max_length=64, verbose_name='名称')
remark = CharField(max_length=256, null=True, blank=True, verbose_name='备注')
team = ForeignKey('system.Team', on_delete=CASCADE, related_name='client_categories')
class Meta:
unique_together = [('name', 'team')]
class SupplierCategory(Model):
"""供应商分类"""
name = CharField(max_length=64, verbose_name='名称')
remark = CharField(max_length=256, null=True, blank=True, verbose_name='备注')
team = ForeignKey('system.Team', on_delete=CASCADE, related_name='supplier_categories')
class Meta:
unique_together = [('name', 'team')]
class GoodsCategory(Model):
"""商品分类"""
name = CharField(max_length=64, verbose_name='名称')
remark = CharField(max_length=256, null=True, blank=True, verbose_name='备注')
team = ForeignKey('system.Team', on_delete=CASCADE, related_name='goods_categories')
class Meta:
unique_together = [('name', 'team')]
class GoodsUnit(Model):
"""商品单位"""
name = CharField(max_length=64, verbose_name='名称')
remark = CharField(max_length=256, null=True, blank=True, verbose_name='备注')
team = ForeignKey('system.Team', on_delete=CASCADE, related_name='goods_units')
class Meta:
unique_together = [('name', 'team')]
__all__ = [ __all__ = [
'Warehouse', 'Client', 'Supplier', 'Account', 'Warehouse', 'ClientCategory', 'Client', 'SupplierCategory', 'Supplier',
'ChargeItem', 'ClientCategory', 'SupplierCategory', 'Account', 'ChargeItem',
'GoodsCategory', 'GoodsUnit',
] ]

View file

@ -1,44 +1,37 @@
from extensions.permissions import InterfacePermission from extensions.permissions import ModelPermission
class WarehousePermission(InterfacePermission): class WarehousePermission(ModelPermission):
code = 'warehouse' code = 'warehouse'
class ClientPermission(InterfacePermission): class ClientPermission(ModelPermission):
code = 'client' code = 'client'
class SupplierPermission(InterfacePermission): class SupplierPermission(ModelPermission):
code = 'supplier' code = 'supplier'
class AccountPermission(InterfacePermission): class AccountPermission(ModelPermission):
code = 'account' code = 'account'
class ChargeItemPermission(InterfacePermission): class ChargeItemPermission(ModelPermission):
code = 'charge_item' code = 'charge_item'
class ClientCategoryPermission(InterfacePermission): class ClientCategoryPermission(ModelPermission):
code = 'client_category' code = 'client_category'
class SupplierCategoryPermission(InterfacePermission): class SupplierCategoryPermission(ModelPermission):
code = 'supplier_category' code = 'supplier_category'
class GoodsCategoryPermission(InterfacePermission):
code = 'goods_category'
class GoodsUnitPermission(InterfacePermission):
code = 'goods_unit'
__all__ = [ __all__ = [
'WarehousePermission', 'ClientPermission', 'SupplierPermission', 'AccountPermission', 'WarehousePermission', 'ClientPermission', 'SupplierPermission', 'AccountPermission',
'ChargeItemPermission', 'ClientCategoryPermission', 'SupplierCategoryPermission', 'ChargeItemPermission', 'ClientCategoryPermission', 'SupplierCategoryPermission',
'GoodsCategoryPermission', 'GoodsUnitPermission',
] ]

View file

@ -1,3 +1,4 @@
from extensions.common.base import *
from extensions.serializers import * from extensions.serializers import *
from extensions.exceptions import * from extensions.exceptions import *
from apps.data.models import * from apps.data.models import *
@ -28,6 +29,17 @@ class WarehouseSerializer(BaseSerializer):
return instance return instance
class ClientCategorySerializer(BaseSerializer):
class Meta:
model = ClientCategory
read_only_fields = ['id']
fields = ['name', 'remark', *read_only_fields]
def validate_name(self, value):
self.validate_unique({'name': value}, message=f'名称[{value}]已存在')
return value
class ClientSerializer(BaseSerializer): class ClientSerializer(BaseSerializer):
level_display = CharField(source='get_level_display', read_only=True, label='等级') level_display = CharField(source='get_level_display', read_only=True, label='等级')
category_name = CharField(source='category.name', read_only=True, label='分类名称') category_name = CharField(source='category.name', read_only=True, label='分类名称')
@ -62,6 +74,17 @@ class ClientSerializer(BaseSerializer):
return super().update(instance, validated_data) return super().update(instance, validated_data)
class SupplierCategorySerializer(BaseSerializer):
class Meta:
model = SupplierCategory
read_only_fields = ['id']
fields = ['name', 'remark', *read_only_fields]
def validate_name(self, value):
self.validate_unique({'name': value}, message=f'名称[{value}]已存在')
return value
class SupplierSerializer(BaseSerializer): class SupplierSerializer(BaseSerializer):
category_name = CharField(source='category.name', read_only=True, label='分类名称') category_name = CharField(source='category.name', read_only=True, label='分类名称')
@ -140,56 +163,12 @@ class ChargeItemSerializer(BaseSerializer):
return value return value
class ClientCategorySerializer(BaseSerializer):
class Meta:
model = ClientCategory
read_only_fields = ['id']
fields = ['name', 'remark', *read_only_fields]
def validate_name(self, value):
self.validate_unique({'name': value}, message=f'名称[{value}]已存在')
return value
class SupplierCategorySerializer(BaseSerializer):
class Meta:
model = SupplierCategory
read_only_fields = ['id']
fields = ['name', 'remark', *read_only_fields]
def validate_name(self, value):
self.validate_unique({'name': value}, message=f'名称[{value}]已存在')
return value
class GoodsCategorySerializer(BaseSerializer):
class Meta:
model = GoodsCategory
read_only_fields = ['id']
fields = ['name', 'remark', *read_only_fields]
def validate_name(self, value):
self.validate_unique({'name': value}, message=f'名称[{value}]已存在')
return value
class GoodsUnitSerializer(BaseSerializer):
class Meta:
model = GoodsUnit
read_only_fields = ['id']
fields = ['name', 'remark', *read_only_fields]
def validate_name(self, value):
self.validate_unique({'name': value}, message=f'名称[{value}]已存在')
return value
__all__ = [ __all__ = [
'WarehouseSerializer', 'ClientSerializer', 'SupplierSerializer', 'AccountSerializer', 'WarehouseSerializer',
'ChargeItemSerializer', 'ClientCategorySerializer', 'SupplierCategorySerializer', 'ClientCategorySerializer', 'ClientSerializer',
'GoodsCategorySerializer', 'GoodsUnitSerializer', 'SupplierCategorySerializer','SupplierSerializer',
'AccountSerializer', 'ChargeItemSerializer',
] ]

View file

@ -4,12 +4,10 @@ from apps.data.views import *
router = BaseRouter() router = BaseRouter()
router.register('warehouses', WarehouseViewSet, 'warehouse') router.register('warehouses', WarehouseViewSet, 'warehouse')
router.register('client_categories', ClientCategoryViewSet, 'client_category')
router.register('clients', ClientViewSet, 'client') router.register('clients', ClientViewSet, 'client')
router.register('supplier_categories', SupplierCategoryViewSet, 'supplier_category')
router.register('suppliers', SupplierViewSet, 'supplier') router.register('suppliers', SupplierViewSet, 'supplier')
router.register('accounts', AccountViewSet, 'account') router.register('accounts', AccountViewSet, 'account')
router.register('charge_items', ChargeItemViewSet, 'charge_item') router.register('charge_items', ChargeItemViewSet, 'charge_item')
router.register('client_categories', ClientCategoryViewSet, 'client_category')
router.register('supplier_categories', SupplierCategoryViewSet, 'supplier_category')
router.register('goods_categories', GoodsCategoryViewSet, 'goods_category')
router.register('goods_units', GoodsUnitViewSet, 'goods_unit')
urlpatterns = router.urls urlpatterns = router.urls

View file

@ -1,3 +1,5 @@
from extensions.common.schema import *
from extensions.common.base import *
from extensions.permissions import * from extensions.permissions import *
from extensions.exceptions import * from extensions.exceptions import *
from extensions.viewsets import * from extensions.viewsets import *
@ -9,7 +11,7 @@ from apps.data.models import *
from apps.goods.models import * from apps.goods.models import *
class WarehouseViewSet(ModelViewSet): class WarehouseViewSet(ModelViewSet, DataProtectMixin):
"""仓库""" """仓库"""
serializer_class = WarehouseSerializer serializer_class = WarehouseSerializer
@ -29,12 +31,6 @@ class WarehouseViewSet(ModelViewSet):
Inventory.objects.bulk_create([Inventory(warehouse=warehouse, goods=goods, team=self.team) Inventory.objects.bulk_create([Inventory(warehouse=warehouse, goods=goods, team=self.team)
for goods in Goods.objects.filter(team=self.team)]) for goods in Goods.objects.filter(team=self.team)])
def perform_destroy(self, instance):
try:
instance.delete()
except ProtectedError:
raise ValidationError(f'仓库[{instance.name}]已被引用, 无法删除')
@extend_schema(responses={200: NumberResponse}) @extend_schema(responses={200: NumberResponse})
@action(detail=False, methods=['get']) @action(detail=False, methods=['get'])
def number(self, request, *args, **kwargs): def number(self, request, *args, **kwargs):
@ -68,7 +64,17 @@ class WarehouseViewSet(ModelViewSet):
return Response(data=serializer.data, status=status.HTTP_200_OK) return Response(data=serializer.data, status=status.HTTP_200_OK)
class ClientViewSet(ModelViewSet): class ClientCategoryViewSet(ModelViewSet):
"""客户分类"""
serializer_class = ClientCategorySerializer
permission_classes = [IsAuthenticated, ClientCategoryPermission]
search_fields = ['name', 'remark']
ordering_fields = ['id', 'name']
queryset = ClientCategory.objects.all()
class ClientViewSet(ModelViewSet, DataProtectMixin):
"""客户""" """客户"""
serializer_class = ClientSerializer serializer_class = ClientSerializer
@ -80,12 +86,6 @@ class ClientViewSet(ModelViewSet):
select_related_fields = ['category'] select_related_fields = ['category']
queryset = Client.objects.all() queryset = Client.objects.all()
def perform_destroy(self, instance):
try:
instance.delete()
except ProtectedError:
raise ValidationError(f'客户[{instance.name}]已被引用, 无法删除')
@extend_schema(responses={200: NumberResponse}) @extend_schema(responses={200: NumberResponse})
@action(detail=False, methods=['get']) @action(detail=False, methods=['get'])
def number(self, request, *args, **kwargs): def number(self, request, *args, **kwargs):
@ -95,7 +95,17 @@ class ClientViewSet(ModelViewSet):
return Response(data={'number': number}, status=status.HTTP_200_OK) return Response(data={'number': number}, status=status.HTTP_200_OK)
class SupplierViewSet(ModelViewSet): class SupplierCategoryViewSet(ModelViewSet):
"""供应商分类"""
serializer_class = SupplierCategorySerializer
permission_classes = [IsAuthenticated, SupplierCategoryPermission]
search_fields = ['name', 'remark']
ordering_fields = ['id', 'name']
queryset = SupplierCategory.objects.all()
class SupplierViewSet(ModelViewSet, DataProtectMixin):
"""供应商""" """供应商"""
serializer_class = SupplierSerializer serializer_class = SupplierSerializer
@ -107,12 +117,6 @@ class SupplierViewSet(ModelViewSet):
select_related_fields = ['category'] select_related_fields = ['category']
queryset = Supplier.objects.all() queryset = Supplier.objects.all()
def perform_destroy(self, instance):
try:
instance.delete()
except ProtectedError:
raise ValidationError(f'供应商[{instance.name}]已被引用, 无法删除')
@extend_schema(responses={200: NumberResponse}) @extend_schema(responses={200: NumberResponse})
@action(detail=False, methods=['get']) @action(detail=False, methods=['get'])
def number(self, request, *args, **kwargs): def number(self, request, *args, **kwargs):
@ -122,7 +126,7 @@ class SupplierViewSet(ModelViewSet):
return Response(data={'number': number}, status=status.HTTP_200_OK) return Response(data={'number': number}, status=status.HTTP_200_OK)
class AccountViewSet(ModelViewSet): class AccountViewSet(ModelViewSet, DataProtectMixin):
"""结算账户""" """结算账户"""
serializer_class = AccountSerializer serializer_class = AccountSerializer
@ -133,12 +137,6 @@ class AccountViewSet(ModelViewSet):
ordering = ['order', 'id'] ordering = ['order', 'id']
queryset = Account.objects.all() queryset = Account.objects.all()
def perform_destroy(self, instance):
try:
instance.delete()
except ProtectedError:
raise ValidationError(f'结算账户[{instance.name}]已被引用, 无法删除')
@extend_schema(responses={200: NumberResponse}) @extend_schema(responses={200: NumberResponse})
@action(detail=False, methods=['get']) @action(detail=False, methods=['get'])
def number(self, request, *args, **kwargs): def number(self, request, *args, **kwargs):
@ -159,48 +157,9 @@ class ChargeItemViewSet(ModelViewSet):
queryset = ChargeItem.objects.all() queryset = ChargeItem.objects.all()
class ClientCategoryViewSet(ModelViewSet):
"""客户分类"""
serializer_class = ClientCategorySerializer
permission_classes = [IsAuthenticated, ClientCategoryPermission]
search_fields = ['name', 'remark']
ordering_fields = ['id', 'name']
queryset = ClientCategory.objects.all()
class SupplierCategoryViewSet(ModelViewSet):
"""供应商分类"""
serializer_class = SupplierCategorySerializer
permission_classes = [IsAuthenticated, SupplierCategoryPermission]
search_fields = ['name', 'remark']
ordering_fields = ['id', 'name']
queryset = SupplierCategory.objects.all()
class GoodsCategoryViewSet(ModelViewSet):
"""商品分类"""
serializer_class = GoodsCategorySerializer
permission_classes = [IsAuthenticated, GoodsCategoryPermission]
search_fields = ['name', 'remark']
ordering_fields = ['id', 'name']
queryset = GoodsCategory.objects.all()
class GoodsUnitViewSet(ModelViewSet):
"""商品单位"""
serializer_class = GoodsUnitSerializer
permission_classes = [IsAuthenticated, GoodsUnitPermission]
search_fields = ['name', 'remark']
ordering_fields = ['id', 'name']
queryset = GoodsUnit.objects.all()
__all__ = [ __all__ = [
'WarehouseViewSet', 'ClientViewSet', 'SupplierViewSet', 'AccountViewSet', 'WarehouseViewSet',
'ChargeItemViewSet', 'ClientCategoryViewSet', 'SupplierCategoryViewSet', 'ClientCategoryViewSet', 'ClientViewSet',
'GoodsCategoryViewSet', 'GoodsUnitViewSet', 'SupplierCategoryViewSet', 'SupplierViewSet',
'AccountViewSet', 'ChargeItemViewSet',
] ]

View file

@ -1,19 +1,19 @@
from extensions.permissions import InterfacePermission from extensions.permissions import ModelPermission
class PaymentOrderPermission(InterfacePermission): class PaymentOrderPermission(ModelPermission):
code = 'payment_order' code = 'payment_order'
class CollectionOrderPermission(InterfacePermission): class CollectionOrderPermission(ModelPermission):
code = 'collection_order' code = 'collection_order'
class ChargeOrderPermission(InterfacePermission): class ChargeOrderPermission(ModelPermission):
code = 'charge_order' code = 'charge_order'
class AccountTransferRecordPermission(InterfacePermission): class AccountTransferRecordPermission(ModelPermission):
code = 'account_transfer_record' code = 'account_transfer_record'

View file

@ -1,3 +1,4 @@
from extensions.common.base import *
from extensions.serializers import * from extensions.serializers import *
from extensions.exceptions import * from extensions.exceptions import *
from apps.finance.models import * from apps.finance.models import *

View file

@ -1,3 +1,5 @@
from extensions.common.schema import *
from extensions.common.base import *
from extensions.permissions import * from extensions.permissions import *
from extensions.exceptions import * from extensions.exceptions import *
from extensions.viewsets import * from extensions.viewsets import *

View file

@ -1,4 +1,4 @@
from extensions.permissions import InterfacePermission from extensions.permissions import ModelPermission
__all__ = [ __all__ = [

View file

@ -1,3 +1,4 @@
from extensions.common.base import *
from extensions.serializers import * from extensions.serializers import *
from extensions.exceptions import * from extensions.exceptions import *

View file

@ -1,3 +1,5 @@
from extensions.common.schema import *
from extensions.common.base import *
from extensions.permissions import * from extensions.permissions import *
from extensions.exceptions import * from extensions.exceptions import *
from extensions.viewsets import * from extensions.viewsets import *

View file

@ -2,4 +2,4 @@ from django.contrib import admin
from apps.goods.models import * from apps.goods.models import *
admin.site.register([Goods, Batch, Inventory]) admin.site.register([GoodsCategory, GoodsUnit, Goods, GoodsImage, Batch, Inventory])

View file

@ -1,15 +1,38 @@
from extensions.common.base import *
from extensions.models import * from extensions.models import *
class GoodsCategory(Model):
"""商品分类"""
name = CharField(max_length=64, verbose_name='名称')
remark = CharField(max_length=256, null=True, blank=True, verbose_name='备注')
team = ForeignKey('system.Team', on_delete=CASCADE, related_name='goods_categories')
class Meta:
unique_together = [('name', 'team')]
class GoodsUnit(Model):
"""商品单位"""
name = CharField(max_length=64, verbose_name='名称')
remark = CharField(max_length=256, null=True, blank=True, verbose_name='备注')
team = ForeignKey('system.Team', on_delete=CASCADE, related_name='goods_units')
class Meta:
unique_together = [('name', 'team')]
class Goods(Model): class Goods(Model):
"""商品""" """商品"""
number = CharField(max_length=32, verbose_name='编号') number = CharField(max_length=32, verbose_name='编号')
name = CharField(max_length=64, verbose_name='名称') name = CharField(max_length=64, verbose_name='名称')
barcode = CharField(max_length=32, null=True, blank=True, verbose_name='条码') barcode = CharField(max_length=32, null=True, blank=True, verbose_name='条码')
category = ForeignKey('data.GoodsCategory', on_delete=SET_NULL, null=True, category = ForeignKey('goods.GoodsCategory', on_delete=SET_NULL, null=True,
related_name='goods_set', verbose_name='商品分类') related_name='goods_set', verbose_name='商品分类')
unit = ForeignKey('data.GoodsUnit', on_delete=SET_NULL, null=True, unit = ForeignKey('goods.GoodsUnit', on_delete=SET_NULL, null=True,
related_name='goods_set', verbose_name='商品单位') related_name='goods_set', verbose_name='商品单位')
spec = CharField(max_length=64, null=True, blank=True, verbose_name='商品规格') spec = CharField(max_length=64, null=True, blank=True, verbose_name='商品规格')
enable_batch_control = BooleanField(default=False, verbose_name='启用批次控制') enable_batch_control = BooleanField(default=False, verbose_name='启用批次控制')
@ -45,6 +68,16 @@ class Goods(Model):
return number return number
class GoodsImage(Model):
"""商品图片"""
goods = ForeignKey('goods.Goods', on_delete=SET_NULL, null=True,
related_name='goods_images', verbose_name='商品')
file = ImageField(verbose_name='文件')
name = CharField(max_length=256, verbose_name='文件名称')
team = ForeignKey('system.Team', on_delete=CASCADE, related_name='goods_images')
class Batch(Model): class Batch(Model):
"""批次""" """批次"""
@ -65,12 +98,6 @@ class Batch(Model):
class Meta: class Meta:
unique_together = [('number', 'warehouse', 'goods', 'team')] unique_together = [('number', 'warehouse', 'goods', 'team')]
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
self.has_stock = self.remain_quantity > 0
if update_fields:
update_fields.append('has_stock')
return super().save(force_insert, force_update, using, update_fields)
class Inventory(Model): class Inventory(Model):
"""库存""" """库存"""
@ -85,13 +112,8 @@ class Inventory(Model):
class Meta: class Meta:
unique_together = [('warehouse', 'goods', 'team')] unique_together = [('warehouse', 'goods', 'team')]
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
self.has_stock = self.total_quantity > 0
if update_fields:
update_fields.append('has_stock')
return super().save(force_insert, force_update, using, update_fields)
__all__ = [ __all__ = [
'Goods', 'Batch', 'Inventory', 'GoodsCategory', 'GoodsUnit', 'Goods', 'GoodsImage',
'Batch', 'Inventory',
] ]

View file

@ -1,18 +1,27 @@
from extensions.permissions import InterfacePermission from extensions.permissions import ModelPermission
class GoodsPermission(InterfacePermission): class GoodsCategoryPermission(ModelPermission):
code = 'goods_category'
class GoodsUnitPermission(ModelPermission):
code = 'goods_unit'
class GoodsPermission(ModelPermission):
code = 'goods' code = 'goods'
class BatchPermission(InterfacePermission): class BatchPermission(ModelPermission):
code = 'batch' code = 'batch'
class InventoryPermission(InterfacePermission): class InventoryPermission(ModelPermission):
code = 'inventory' code = 'inventory'
__all__ = [ __all__ = [
'GoodsPermission', 'BatchPermission', 'InventoryPermission', 'GoodsCategoryPermission', 'GoodsUnitPermission', 'GoodsPermission',
'BatchPermission', 'InventoryPermission',
] ]

View file

@ -1,15 +1,40 @@
from extensions.common.base import *
from extensions.serializers import * from extensions.serializers import *
from extensions.exceptions import * from extensions.exceptions import *
from apps.goods.models import * from apps.goods.models import *
from apps.data.models import * from apps.data.models import *
class GoodsCategorySerializer(BaseSerializer):
class Meta:
model = GoodsCategory
read_only_fields = ['id']
fields = ['name', 'remark', *read_only_fields]
def validate_name(self, value):
self.validate_unique({'name': value}, message=f'名称[{value}]已存在')
return value
class GoodsUnitSerializer(BaseSerializer):
class Meta:
model = GoodsUnit
read_only_fields = ['id']
fields = ['name', 'remark', *read_only_fields]
def validate_name(self, value):
self.validate_unique({'name': value}, message=f'名称[{value}]已存在')
return value
class GoodsSerializer(BaseSerializer): class GoodsSerializer(BaseSerializer):
class InventorySerializer(BaseSerializer): class InventoryItemSerializer(BaseSerializer):
class BatchItemSerializer(BaseSerializer):
class BatchSerializer(BaseSerializer):
class Meta: class Meta:
model = Batch model = Batch
read_only_fields = ['id'] read_only_fields = ['id']
@ -26,7 +51,7 @@ class GoodsSerializer(BaseSerializer):
warehouse_number = CharField(source='warehouse.number', read_only=True, label='仓库编号') warehouse_number = CharField(source='warehouse.number', read_only=True, label='仓库编号')
warehouse_name = CharField(source='warehouse.name', read_only=True, label='仓库名称') warehouse_name = CharField(source='warehouse.name', read_only=True, label='仓库名称')
batch_items = BatchSerializer(source='batchs', required=False, many=True, label='批次') batch_items = BatchItemSerializer(source='batchs', required=False, many=True, label='批次Item')
class Meta: class Meta:
model = Inventory model = Inventory
@ -42,18 +67,28 @@ class GoodsSerializer(BaseSerializer):
raise ValidationError('库存数量小于零') raise ValidationError('库存数量小于零')
return value return value
class GoodsImageItemSerializer(BaseSerializer):
class Meta:
model = GoodsImage
fields = ['id', 'name', 'file']
category_name = CharField(source='category.name', read_only=True, label='分类名称') category_name = CharField(source='category.name', read_only=True, label='分类名称')
unit_name = CharField(source='unit.name', read_only=True, label='单位名称') unit_name = CharField(source='unit.name', read_only=True, label='单位名称')
inventory_items = InventorySerializer(source='inventories', required=False, many=True, label='库存') inventory_items = InventoryItemSerializer(source='inventories', required=False,
many=True, label='库存Item')
goods_image_items = GoodsImageItemSerializer(source='goods_images', many=True,
read_only=True, label='商品图片Item')
class Meta: class Meta:
model = Goods model = Goods
read_only_fields = ['id', 'category_name', 'unit_name'] read_only_fields = ['id', 'category_name', 'unit_name', 'goods_image_items']
fields = ['number', 'name', 'barcode', 'category', 'unit', 'spec', 'enable_batch_control', fields = ['number', 'name', 'barcode', 'category', 'unit', 'spec', 'enable_batch_control',
'shelf_life_days', 'shelf_life_warning_days', 'enable_inventory_warning', 'shelf_life_days', 'shelf_life_warning_days', 'enable_inventory_warning',
'inventory_upper', 'inventory_lower', 'purchase_price', 'retail_price', 'inventory_upper', 'inventory_lower', 'purchase_price', 'retail_price',
'level_price1', 'level_price2', 'level_price3', 'remark', 'order', 'level_price1', 'level_price2', 'level_price3', 'remark', 'order',
'is_active', 'inventory_items', *read_only_fields] 'is_active', 'inventory_items', 'goods_images', *read_only_fields]
extra_kwargs = {'goods_images': {'required': False}}
def validate_number(self, value): def validate_number(self, value):
self.validate_unique({'number': value}, message=f'编号[{value}]已存在') self.validate_unique({'number': value}, message=f'编号[{value}]已存在')
@ -67,6 +102,10 @@ class GoodsSerializer(BaseSerializer):
instance = self.validate_foreign_key(GoodsUnit, instance, message='商品单位不存在') instance = self.validate_foreign_key(GoodsUnit, instance, message='商品单位不存在')
return instance return instance
def validate_goods_images(self, instances):
instances = self.validate_foreign_key_set(GoodsImage, instances, message='商品图片不存在')
return instances
@transaction.atomic @transaction.atomic
def create(self, validated_data): def create(self, validated_data):
inventory_items = validated_data.pop('inventories', []) inventory_items = validated_data.pop('inventories', [])
@ -101,7 +140,7 @@ class GoodsSerializer(BaseSerializer):
production_date=production_date, shelf_life_days=goods.shelf_life_days, production_date=production_date, shelf_life_days=goods.shelf_life_days,
expiration_date=expiration_date, initial_inventory=inventory, team=self.team, expiration_date=expiration_date, initial_inventory=inventory, team=self.team,
)) ))
total_inventory_quantity = NP.plus(total_inventory_quantity, total_quantity) total_inventory_quantity = NP.plus(total_inventory_quantity, total_quantity)
break break
else: else:
@ -146,6 +185,18 @@ class GoodsSerializer(BaseSerializer):
return goods return goods
class GoodsImageSerializer(BaseSerializer):
class Meta:
model = GoodsImage
read_only_fields = ['id', 'name']
fields = ['file', *read_only_fields]
def create(self, validated_data):
validated_data['name'] = validated_data['file'].name
return super().create(validated_data)
class BatchSerializer(BaseSerializer): class BatchSerializer(BaseSerializer):
warehouse_number = CharField(source='warehouse.number', read_only=True, label='仓库编号') warehouse_number = CharField(source='warehouse.number', read_only=True, label='仓库编号')
warehouse_name = CharField(source='warehouse.name', read_only=True, label='仓库名称') warehouse_name = CharField(source='warehouse.name', read_only=True, label='仓库名称')
@ -176,5 +227,6 @@ class InventorySerializer(BaseSerializer):
__all__ = [ __all__ = [
'GoodsSerializer', 'BatchSerializer', 'InventorySerializer', 'GoodsCategorySerializer', 'GoodsUnitSerializer', 'GoodsSerializer', 'GoodsImageSerializer',
'BatchSerializer', 'InventorySerializer',
] ]

View file

@ -3,7 +3,10 @@ from apps.goods.views import *
router = BaseRouter() router = BaseRouter()
router.register('goods_categories', GoodsCategoryViewSet, 'goods_category')
router.register('goods_units', GoodsUnitViewSet, 'goods_unit')
router.register('goods', GoodsViewSet, 'goods') router.register('goods', GoodsViewSet, 'goods')
router.register('goods_images', GoodsViewSet, 'goods_image')
router.register('batchs', BatchViewSet, 'batch') router.register('batchs', BatchViewSet, 'batch')
router.register('inventories', InventoryViewSet, 'inventory') router.register('inventories', InventoryViewSet, 'inventory')
urlpatterns = router.urls urlpatterns = router.urls

View file

@ -1,3 +1,5 @@
from extensions.common.schema import *
from extensions.common.base import *
from extensions.permissions import * from extensions.permissions import *
from extensions.exceptions import * from extensions.exceptions import *
from extensions.viewsets import * from extensions.viewsets import *
@ -9,7 +11,27 @@ from apps.goods.models import *
from apps.data.models import * from apps.data.models import *
class GoodsViewSet(ModelViewSet): class GoodsCategoryViewSet(ModelViewSet):
"""商品分类"""
serializer_class = GoodsCategorySerializer
permission_classes = [IsAuthenticated, GoodsCategoryPermission]
search_fields = ['name', 'remark']
ordering_fields = ['id', 'name']
queryset = GoodsCategory.objects.all()
class GoodsUnitViewSet(ModelViewSet):
"""商品单位"""
serializer_class = GoodsUnitSerializer
permission_classes = [IsAuthenticated, GoodsUnitPermission]
search_fields = ['name', 'remark']
ordering_fields = ['id', 'name']
queryset = GoodsUnit.objects.all()
class GoodsViewSet(ModelViewSet, DataProtectMixin):
"""商品""" """商品"""
serializer_class = GoodsSerializer serializer_class = GoodsSerializer
@ -22,12 +44,6 @@ class GoodsViewSet(ModelViewSet):
prefetch_related_fields = ['inventories', 'inventories__batchs'] prefetch_related_fields = ['inventories', 'inventories__batchs']
queryset = Goods.objects.all() queryset = Goods.objects.all()
def perform_destroy(self, instance):
try:
instance.delete()
except ProtectedError:
raise ValidationError(f'商品[{instance.name}]已被引用, 无法删除')
@extend_schema(responses={200: NumberResponse}) @extend_schema(responses={200: NumberResponse})
@action(detail=False, methods=['get']) @action(detail=False, methods=['get'])
def number(self, request, *args, **kwargs): def number(self, request, *args, **kwargs):
@ -37,6 +53,15 @@ class GoodsViewSet(ModelViewSet):
return Response(data={'number': number}, status=status.HTTP_200_OK) return Response(data={'number': number}, status=status.HTTP_200_OK)
class GoodsImageViewSet(ModelViewSet):
"""商品图片"""
serializer_class = GoodsImageSerializer
permission_classes = [IsAuthenticated, GoodsPermission]
search_fields = ['name']
queryset = GoodsImage.objects.all()
class BatchViewSet(BaseViewSet, ReadOnlyMixin): class BatchViewSet(BaseViewSet, ReadOnlyMixin):
"""批次""" """批次"""
@ -63,5 +88,6 @@ class InventoryViewSet(BaseViewSet, ReadOnlyMixin):
__all__ = [ __all__ = [
'GoodsViewSet', 'BatchViewSet', 'InventoryViewSet', 'GoodsCategoryViewSet', 'GoodsUnitViewSet', 'GoodsViewSet', 'GoodsImageViewSet',
'BatchViewSet', 'InventoryViewSet',
] ]

View file

@ -1,3 +1,4 @@
from extensions.common.base import *
from extensions.serializers import * from extensions.serializers import *
from extensions.exceptions import * from extensions.exceptions import *
from apps.system.models import * from apps.system.models import *

View file

@ -1,3 +1,5 @@
from extensions.common.schema import *
from extensions.common.base import *
from extensions.permissions import * from extensions.permissions import *
from extensions.exceptions import * from extensions.exceptions import *
from extensions.viewsets import * from extensions.viewsets import *

View file

@ -1,11 +1,11 @@
from extensions.permissions import InterfacePermission from extensions.permissions import ModelPermission
class PurchaseOrderPermission(InterfacePermission): class PurchaseOrderPermission(ModelPermission):
code = 'purchase_order' code = 'purchase_order'
class PurchaseReturnOrderPermission(InterfacePermission): class PurchaseReturnOrderPermission(ModelPermission):
code = 'purchase_return_order' code = 'purchase_return_order'

View file

@ -1,3 +1,4 @@
from extensions.common.base import *
from extensions.serializers import * from extensions.serializers import *
from extensions.exceptions import * from extensions.exceptions import *
from apps.purchase.models import * from apps.purchase.models import *

View file

@ -1,3 +1,5 @@
from extensions.common.schema import *
from extensions.common.base import *
from extensions.permissions import * from extensions.permissions import *
from extensions.exceptions import * from extensions.exceptions import *
from extensions.viewsets import * from extensions.viewsets import *

View file

@ -1,11 +1,11 @@
from extensions.permissions import InterfacePermission from extensions.permissions import ModelPermission
class SalesOrderPermission(InterfacePermission): class SalesOrderPermission(ModelPermission):
code = 'sales_order' code = 'sales_order'
class SalesReturnOrderPermission(InterfacePermission): class SalesReturnOrderPermission(ModelPermission):
code = 'sales_return_order' code = 'sales_return_order'

View file

@ -1,3 +1,4 @@
from extensions.common.base import *
from extensions.serializers import * from extensions.serializers import *
from extensions.exceptions import * from extensions.exceptions import *
from apps.sales.models import * from apps.sales.models import *

View file

@ -1,3 +1,5 @@
from extensions.common.schema import *
from extensions.common.base import *
from extensions.permissions import * from extensions.permissions import *
from extensions.exceptions import * from extensions.exceptions import *
from extensions.viewsets import * from extensions.viewsets import *

View file

@ -1,4 +1,4 @@
from extensions.permissions import InterfacePermission from extensions.permissions import ModelPermission
__all__ = [ __all__ = [

View file

@ -1,3 +1,4 @@
from extensions.common.base import *
from extensions.serializers import * from extensions.serializers import *
from extensions.exceptions import * from extensions.exceptions import *

View file

@ -1,3 +1,5 @@
from extensions.common.schema import *
from extensions.common.base import *
from extensions.permissions import * from extensions.permissions import *
from extensions.exceptions import * from extensions.exceptions import *
from extensions.viewsets import * from extensions.viewsets import *

View file

@ -1,4 +1,4 @@
from extensions.permissions import InterfacePermission from extensions.permissions import ModelPermission
__all__ = [ __all__ = [

View file

@ -1,3 +1,4 @@
from extensions.common.base import *
from extensions.serializers import * from extensions.serializers import *
from extensions.exceptions import * from extensions.exceptions import *

View file

@ -1,3 +1,5 @@
from extensions.common.schema import *
from extensions.common.base import *
from extensions.permissions import * from extensions.permissions import *
from extensions.exceptions import * from extensions.exceptions import *
from extensions.viewsets import * from extensions.viewsets import *

View file

@ -1,7 +1,7 @@
from extensions.permissions import InterfacePermission from extensions.permissions import ModelPermission
class StockInPermission(InterfacePermission): class StockInPermission(ModelPermission):
code = 'stock_in' code = 'stock_in'

View file

@ -1,4 +1,5 @@
from apps.goods.models import Batch from apps.goods.models import Batch
from extensions.common.base import *
from extensions.serializers import * from extensions.serializers import *
from extensions.exceptions import * from extensions.exceptions import *
from apps.stock_in.models import * from apps.stock_in.models import *

View file

@ -1,3 +1,5 @@
from extensions.common.schema import *
from extensions.common.base import *
from extensions.permissions import * from extensions.permissions import *
from extensions.exceptions import * from extensions.exceptions import *
from extensions.viewsets import * from extensions.viewsets import *

View file

@ -1,7 +1,7 @@
from extensions.permissions import InterfacePermission from extensions.permissions import ModelPermission
class StockOutPermission(InterfacePermission): class StockOutPermission(ModelPermission):
code = 'stock_out' code = 'stock_out'

View file

@ -1,3 +1,4 @@
from extensions.common.base import *
from extensions.serializers import * from extensions.serializers import *
from extensions.exceptions import * from extensions.exceptions import *
from apps.stock_out.models import * from apps.stock_out.models import *

View file

@ -1,3 +1,5 @@
from extensions.common.schema import *
from extensions.common.base import *
from extensions.permissions import * from extensions.permissions import *
from extensions.exceptions import * from extensions.exceptions import *
from extensions.viewsets import * from extensions.viewsets import *

View file

@ -1,7 +1,7 @@
from extensions.permissions import InterfacePermission from extensions.permissions import ModelPermission
class StockTransferPermission(InterfacePermission): class StockTransferPermission(ModelPermission):
code = 'stock_transfer' code = 'stock_transfer'

View file

@ -1,3 +1,4 @@
from extensions.common.base import *
from extensions.serializers import * from extensions.serializers import *
from extensions.exceptions import * from extensions.exceptions import *
from apps.stock_transfer.models import * from apps.stock_transfer.models import *

View file

@ -1,3 +1,5 @@
from extensions.common.schema import *
from extensions.common.base import *
from extensions.permissions import * from extensions.permissions import *
from extensions.exceptions import * from extensions.exceptions import *
from extensions.viewsets import * from extensions.viewsets import *

View file

@ -1,4 +1,4 @@
from extensions.permissions import InterfacePermission from extensions.permissions import ModelPermission
__all__ = [ __all__ = [

View file

@ -1,4 +1,5 @@
from django.contrib.auth.hashers import make_password from django.contrib.auth.hashers import make_password
from extensions.common.base import *
from extensions.serializers import * from extensions.serializers import *
from extensions.exceptions import * from extensions.exceptions import *
from apps.system.models import * from apps.system.models import *

View file

@ -3,6 +3,8 @@ from rest_framework_simplejwt.exceptions import TokenError
from rest_framework_simplejwt.tokens import RefreshToken from rest_framework_simplejwt.tokens import RefreshToken
from extensions.common.base import * from extensions.common.base import *
from extensions.common.schema import * from extensions.common.schema import *
from extensions.common.schema import *
from extensions.common.base import *
from extensions.permissions import * from extensions.permissions import *
from extensions.exceptions import * from extensions.exceptions import *
from extensions.viewsets import * from extensions.viewsets import *

View file

@ -1,7 +1,7 @@
# 常用命令 # 常用命令
## 接口文档
## 接口文档
``` ```
http://127.0.0.1:8000/api/schema/swagger-ui/ http://127.0.0.1:8000/api/schema/swagger-ui/
http://127.0.0.1:8000/api/schema/redoc/ http://127.0.0.1:8000/api/schema/redoc/
@ -10,7 +10,6 @@ http://127.0.0.1:8000/admin/
``` ```
## 构建 ## 构建
``` ```
python manage.py makemigrations python manage.py makemigrations
python manage.py migrate python manage.py migrate
@ -18,7 +17,6 @@ python manage.py shell_plus
``` ```
## 启动 ## 启动
``` ```
python tools/create_configs.py python tools/create_configs.py
python tools/rebuild_database.py python tools/rebuild_database.py

View file

@ -0,0 +1,25 @@
# 商品信息
## 功能
- 查询/创建商品:
[/api/goods/]
- 编辑/删除商品:
[/api/goods/{id}/]
## 其他接口
- 商品分类选项:
[/api/goods_categories/options/]
- 商品单位选项:
[/api/goods_units/options/]
- 上传商品图片:
[/api/goods_images/]
- 仓库选项:
[/api/warehouses/options/]

View file

@ -0,0 +1,13 @@
# 商品分类
## 功能
- 查询/创建商品分类:
[/api/goods_categories/]
- 编辑/删除商品分类:
[/api/goods_categories/{id}/]
## 其他接口

View file

@ -0,0 +1,13 @@
# 商品单位
## 功能
- 查询/创建商品单位:
[/api/goods_units/]
- 编辑/删除商品单位:
[/api/goods_units/{id}/]
## 其他接口

View file

@ -0,0 +1,16 @@
# 仓库
## 功能
- 查询/创建仓库:
[/api/warehouses/]
- 编辑/删除仓库:
[/api/warehouses/{id}/]
## 其他接口
- 管理员选项:
[/api/users/options/]

View file

@ -0,0 +1,16 @@
# 供应商
## 功能
- 查询/创建供应商:
[/api/suppliers/]
- 编辑/删除供应商:
[/api/suppliers/{id}/]
## 其他接口
- 供应商分类选项:
[/api/supplier_categories/options/]

View file

@ -0,0 +1,13 @@
# 供应商分类
## 功能
- 查询/创建供应商分类:
[/api/supplier_categories/]
- 编辑/删除供应商分类:
[/api/supplier_categories/{id}/]
## 其他接口

View file

@ -0,0 +1,16 @@
# 客户
## 功能
- 查询/创建客户:
[/api/suppliers/]
- 编辑/删除客户:
[/api/suppliers/{id}/]
## 其他接口
- 客户分类选项:
[/api/client_categories/options/]

View file

@ -0,0 +1,13 @@
# 客户分类
## 功能
- 查询/创建客户分类:
[/api/client_categories/]
- 编辑/删除客户分类:
[/api/client_categories/{id}/]
## 其他接口

View file

@ -0,0 +1,13 @@
# 收支项目
## 功能
- 查询/创建收支项目:
[/api/charge_items/]
- 编辑/删除收支项目:
[/api/charge_items/{id}/]
## 其他接口

View file

@ -0,0 +1,13 @@
# 结算账户
## 功能
- 查询/创建结算账户:
[/api/accounts/]
- 编辑/删除结算账户:
[/api/accounts/{id}/]
## 其他接口

View file

@ -0,0 +1,13 @@
# 库存报表
## 功能
- 查询库存
[/api/inventories/]
## 其他接口
- 仓库选项:
[/api/warehouses/options/]

View file

@ -0,0 +1,13 @@
# 批次报表
## 功能
- 查询批次
[/api/batchs/]
## 其他接口
- 仓库选项:
[/api/warehouses/options/]

View file

@ -1,7 +1,7 @@
# 员工账号 # 员工账号
# 功能 ## 功能
- 查询/创建账号: - 查询/创建账号:
[/api/users/] [/api/users/]
@ -25,7 +25,7 @@
[/api/user/set_password/] [/api/user/set_password/]
# 其他接口 ## 其他接口
- 角色选项: - 角色选项:
[/api/roles/options/] [/api/roles/options/]

View file

@ -1,7 +1,7 @@
# 系统配置 # 系统配置
# 功能 ## 功能
- 查询系统配置: - 查询系统配置:
[/api/system/configs/] [/api/system/configs/]
@ -10,4 +10,4 @@
[/api/system/set_configs/] [/api/system/set_configs/]
# 其他接口 ## 其他接口

View file

@ -1,7 +1,7 @@
# 角色权限 # 角色权限
# 功能 ## 功能
- 查询/创建角色: - 查询/创建角色:
[/api/roles/] [/api/roles/]
@ -10,7 +10,7 @@
[/api/roles/{id}/] [/api/roles/{id}/]
# 其他接口 ## 其他接口
- 权限列表: - 权限列表:
[/api/permission_groups] [/api/permission_groups]

View file

@ -40,8 +40,8 @@ INSTALLED_APPS = [
'debug_toolbar', 'debug_toolbar',
'apps.system', 'apps.system',
# 'apps.data', 'apps.data',
# 'apps.goods', 'apps.goods',
# 'apps.purchase', # 'apps.purchase',
# 'apps.sales', # 'apps.sales',
# 'apps.stock_in', # 'apps.stock_in',

View file

@ -32,8 +32,8 @@ urlpatterns = [
*static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT), *static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT),
path('api/', include('apps.system.urls')), path('api/', include('apps.system.urls')),
# path('api/', include('apps.data.urls')), path('api/', include('apps.data.urls')),
# path('api/', include('apps.goods.urls')), path('api/', include('apps.goods.urls')),
# path('api/', include('apps.purchase.urls')), # path('api/', include('apps.purchase.urls')),
# path('api/', include('apps.sales.urls')), # path('api/', include('apps.sales.urls')),
# path('api/', include('apps.stock_in.urls')), # path('api/', include('apps.stock_in.urls')),

View file

@ -1,4 +1,4 @@
from extensions.permissions import InterfacePermission from extensions.permissions import ModelPermission
__all__ = [ __all__ = [

View file

@ -1,3 +1,4 @@
from extensions.common.base import *
from extensions.serializers import * from extensions.serializers import *
from extensions.exceptions import * from extensions.exceptions import *

View file

@ -1,3 +1,5 @@
from extensions.common.schema import *
from extensions.common.base import *
from extensions.permissions import * from extensions.permissions import *
from extensions.exceptions import * from extensions.exceptions import *
from extensions.viewsets import * from extensions.viewsets import *