mirror of
https://github.com/himool/HimoolERP.git
synced 2024-09-20 06:46:00 +08:00
feat: 项目文档
This commit is contained in:
parent
453c9de0d8
commit
91ef89f18b
|
@ -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])
|
||||||
|
|
|
@ -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',
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -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',
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -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',
|
||||||
]
|
]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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',
|
||||||
]
|
]
|
||||||
|
|
|
@ -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'
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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 *
|
||||||
|
|
|
@ -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 *
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from extensions.permissions import InterfacePermission
|
from extensions.permissions import ModelPermission
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
|
|
@ -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 *
|
||||||
|
|
||||||
|
|
|
@ -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 *
|
||||||
|
|
|
@ -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])
|
||||||
|
|
|
@ -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',
|
||||||
]
|
]
|
||||||
|
|
|
@ -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',
|
||||||
]
|
]
|
||||||
|
|
|
@ -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',
|
||||||
]
|
]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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',
|
||||||
]
|
]
|
||||||
|
|
|
@ -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 *
|
||||||
|
|
|
@ -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 *
|
||||||
|
|
|
@ -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'
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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 *
|
||||||
|
|
|
@ -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 *
|
||||||
|
|
|
@ -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'
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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 *
|
||||||
|
|
|
@ -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 *
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from extensions.permissions import InterfacePermission
|
from extensions.permissions import ModelPermission
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
|
|
@ -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 *
|
||||||
|
|
||||||
|
|
|
@ -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 *
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from extensions.permissions import InterfacePermission
|
from extensions.permissions import ModelPermission
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
|
|
@ -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 *
|
||||||
|
|
||||||
|
|
|
@ -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 *
|
||||||
|
|
|
@ -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'
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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 *
|
||||||
|
|
|
@ -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 *
|
||||||
|
|
|
@ -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'
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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 *
|
||||||
|
|
|
@ -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 *
|
||||||
|
|
|
@ -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'
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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 *
|
||||||
|
|
|
@ -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 *
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from extensions.permissions import InterfacePermission
|
from extensions.permissions import ModelPermission
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
|
|
@ -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 *
|
||||||
|
|
|
@ -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 *
|
||||||
|
|
|
@ -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
|
||||||
|
|
25
documents/项目文档/商品管理/商品信息.md
Normal file
25
documents/项目文档/商品管理/商品信息.md
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# 商品信息
|
||||||
|
|
||||||
|
|
||||||
|
## 功能
|
||||||
|
|
||||||
|
- 查询/创建商品:
|
||||||
|
[/api/goods/]
|
||||||
|
|
||||||
|
- 编辑/删除商品:
|
||||||
|
[/api/goods/{id}/]
|
||||||
|
|
||||||
|
|
||||||
|
## 其他接口
|
||||||
|
|
||||||
|
- 商品分类选项:
|
||||||
|
[/api/goods_categories/options/]
|
||||||
|
|
||||||
|
- 商品单位选项:
|
||||||
|
[/api/goods_units/options/]
|
||||||
|
|
||||||
|
- 上传商品图片:
|
||||||
|
[/api/goods_images/]
|
||||||
|
|
||||||
|
- 仓库选项:
|
||||||
|
[/api/warehouses/options/]
|
13
documents/项目文档/商品管理/商品分类.md
Normal file
13
documents/项目文档/商品管理/商品分类.md
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# 商品分类
|
||||||
|
|
||||||
|
|
||||||
|
## 功能
|
||||||
|
|
||||||
|
- 查询/创建商品分类:
|
||||||
|
[/api/goods_categories/]
|
||||||
|
|
||||||
|
- 编辑/删除商品分类:
|
||||||
|
[/api/goods_categories/{id}/]
|
||||||
|
|
||||||
|
|
||||||
|
## 其他接口
|
13
documents/项目文档/商品管理/商品单位.md
Normal file
13
documents/项目文档/商品管理/商品单位.md
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# 商品单位
|
||||||
|
|
||||||
|
|
||||||
|
## 功能
|
||||||
|
|
||||||
|
- 查询/创建商品单位:
|
||||||
|
[/api/goods_units/]
|
||||||
|
|
||||||
|
- 编辑/删除商品单位:
|
||||||
|
[/api/goods_units/{id}/]
|
||||||
|
|
||||||
|
|
||||||
|
## 其他接口
|
16
documents/项目文档/基础数据/仓库.md
Normal file
16
documents/项目文档/基础数据/仓库.md
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# 仓库
|
||||||
|
|
||||||
|
|
||||||
|
## 功能
|
||||||
|
|
||||||
|
- 查询/创建仓库:
|
||||||
|
[/api/warehouses/]
|
||||||
|
|
||||||
|
- 编辑/删除仓库:
|
||||||
|
[/api/warehouses/{id}/]
|
||||||
|
|
||||||
|
|
||||||
|
## 其他接口
|
||||||
|
|
||||||
|
- 管理员选项:
|
||||||
|
[/api/users/options/]
|
16
documents/项目文档/基础数据/供应商.md
Normal file
16
documents/项目文档/基础数据/供应商.md
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# 供应商
|
||||||
|
|
||||||
|
|
||||||
|
## 功能
|
||||||
|
|
||||||
|
- 查询/创建供应商:
|
||||||
|
[/api/suppliers/]
|
||||||
|
|
||||||
|
- 编辑/删除供应商:
|
||||||
|
[/api/suppliers/{id}/]
|
||||||
|
|
||||||
|
|
||||||
|
## 其他接口
|
||||||
|
|
||||||
|
- 供应商分类选项:
|
||||||
|
[/api/supplier_categories/options/]
|
13
documents/项目文档/基础数据/供应商分类.md
Normal file
13
documents/项目文档/基础数据/供应商分类.md
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# 供应商分类
|
||||||
|
|
||||||
|
|
||||||
|
## 功能
|
||||||
|
|
||||||
|
- 查询/创建供应商分类:
|
||||||
|
[/api/supplier_categories/]
|
||||||
|
|
||||||
|
- 编辑/删除供应商分类:
|
||||||
|
[/api/supplier_categories/{id}/]
|
||||||
|
|
||||||
|
|
||||||
|
## 其他接口
|
16
documents/项目文档/基础数据/客户.md
Normal file
16
documents/项目文档/基础数据/客户.md
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# 客户
|
||||||
|
|
||||||
|
|
||||||
|
## 功能
|
||||||
|
|
||||||
|
- 查询/创建客户:
|
||||||
|
[/api/suppliers/]
|
||||||
|
|
||||||
|
- 编辑/删除客户:
|
||||||
|
[/api/suppliers/{id}/]
|
||||||
|
|
||||||
|
|
||||||
|
## 其他接口
|
||||||
|
|
||||||
|
- 客户分类选项:
|
||||||
|
[/api/client_categories/options/]
|
13
documents/项目文档/基础数据/客户分类.md
Normal file
13
documents/项目文档/基础数据/客户分类.md
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# 客户分类
|
||||||
|
|
||||||
|
|
||||||
|
## 功能
|
||||||
|
|
||||||
|
- 查询/创建客户分类:
|
||||||
|
[/api/client_categories/]
|
||||||
|
|
||||||
|
- 编辑/删除客户分类:
|
||||||
|
[/api/client_categories/{id}/]
|
||||||
|
|
||||||
|
|
||||||
|
## 其他接口
|
13
documents/项目文档/基础数据/收支项目.md
Normal file
13
documents/项目文档/基础数据/收支项目.md
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# 收支项目
|
||||||
|
|
||||||
|
|
||||||
|
## 功能
|
||||||
|
|
||||||
|
- 查询/创建收支项目:
|
||||||
|
[/api/charge_items/]
|
||||||
|
|
||||||
|
- 编辑/删除收支项目:
|
||||||
|
[/api/charge_items/{id}/]
|
||||||
|
|
||||||
|
|
||||||
|
## 其他接口
|
13
documents/项目文档/基础数据/结算账户.md
Normal file
13
documents/项目文档/基础数据/结算账户.md
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# 结算账户
|
||||||
|
|
||||||
|
|
||||||
|
## 功能
|
||||||
|
|
||||||
|
- 查询/创建结算账户:
|
||||||
|
[/api/accounts/]
|
||||||
|
|
||||||
|
- 编辑/删除结算账户:
|
||||||
|
[/api/accounts/{id}/]
|
||||||
|
|
||||||
|
|
||||||
|
## 其他接口
|
13
documents/项目文档/报表统计/库存报表.md
Normal file
13
documents/项目文档/报表统计/库存报表.md
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# 库存报表
|
||||||
|
|
||||||
|
|
||||||
|
## 功能
|
||||||
|
|
||||||
|
- 查询库存
|
||||||
|
[/api/inventories/]
|
||||||
|
|
||||||
|
|
||||||
|
## 其他接口
|
||||||
|
|
||||||
|
- 仓库选项:
|
||||||
|
[/api/warehouses/options/]
|
13
documents/项目文档/报表统计/批次报表.md
Normal file
13
documents/项目文档/报表统计/批次报表.md
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# 批次报表
|
||||||
|
|
||||||
|
|
||||||
|
## 功能
|
||||||
|
|
||||||
|
- 查询批次
|
||||||
|
[/api/batchs/]
|
||||||
|
|
||||||
|
|
||||||
|
## 其他接口
|
||||||
|
|
||||||
|
- 仓库选项:
|
||||||
|
[/api/warehouses/options/]
|
|
@ -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/]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# 系统配置
|
# 系统配置
|
||||||
|
|
||||||
|
|
||||||
# 功能
|
## 功能
|
||||||
|
|
||||||
- 查询系统配置:
|
- 查询系统配置:
|
||||||
[/api/system/configs/]
|
[/api/system/configs/]
|
||||||
|
@ -10,4 +10,4 @@
|
||||||
[/api/system/set_configs/]
|
[/api/system/set_configs/]
|
||||||
|
|
||||||
|
|
||||||
# 其他接口
|
## 其他接口
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# 角色权限
|
# 角色权限
|
||||||
|
|
||||||
|
|
||||||
# 功能
|
## 功能
|
||||||
|
|
||||||
- 查询/创建角色:
|
- 查询/创建角色:
|
||||||
[/api/roles/]
|
[/api/roles/]
|
||||||
|
@ -10,7 +10,7 @@
|
||||||
[/api/roles/{id}/]
|
[/api/roles/{id}/]
|
||||||
|
|
||||||
|
|
||||||
# 其他接口
|
## 其他接口
|
||||||
|
|
||||||
- 权限列表:
|
- 权限列表:
|
||||||
[/api/permission_groups]
|
[/api/permission_groups]
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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')),
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from extensions.permissions import InterfacePermission
|
from extensions.permissions import ModelPermission
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
|
|
@ -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 *
|
||||||
|
|
||||||
|
|
|
@ -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 *
|
||||||
|
|
Loading…
Reference in a new issue