views.py 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961
  1. from rest_framework import viewsets
  2. from utils.page import MyPageNumberPagination
  3. from utils.datasolve import sumOfList, transportation_calculate
  4. from utils.md5 import Md5
  5. from rest_framework.filters import OrderingFilter
  6. from django_filters.rest_framework import DjangoFilterBackend
  7. from django.db import transaction
  8. from rest_framework.response import Response
  9. from rest_framework.exceptions import APIException
  10. from django.utils import timezone
  11. from django.db.models import Sum
  12. from .models import BoundListModel, BoundDetailModel,BoundBatchModel, BatchLogModel, OutBatchModel,OutBoundDetailModel,MaterialStatistics,OutBoundDemandModel
  13. # from .files import FileListRenderCN, FileDetailRenderCN
  14. from .serializers import BoundListGetSerializer,BoundListPostSerializer,BoundBatchGetSerializer,BoundBatchPostSerializer,BoundDetailGetSerializer,BoundDetailPostSerializer
  15. from .serializers import OutBoundDetailGetSerializer,OutBoundDetailPostSerializer,OutBatchGetSerializer,OutBatchPostSerializer,BatchLogGetSerializer
  16. from .serializers import MaterialStatisticsSerializer,MaterialStatisticsSerializer_items
  17. from .serializers import OutBoundDemandModelSerializer
  18. from .filter import BoundListFilter, BoundDetailFilter,BoundBatchFilter
  19. from .filter import OutBatchFilter,OutBoundDetailFilter,BatchlogFilter
  20. from .filter import MaterialStatisticsFilter
  21. from .filter import OutBoundDemandFilter
  22. # 以后添加模块检验
  23. from warehouse.models import ListModel as warehouse
  24. from staff.models import ListModel as staff
  25. from rest_framework.permissions import AllowAny
  26. from rest_framework.views import APIView
  27. class OutBoundDemandViewSet(viewsets.ModelViewSet):
  28. """
  29. retrieve:
  30. Response a data list(get)
  31. list:
  32. Response a data list(all)
  33. create:
  34. Create a data line(post)
  35. delete:
  36. Delete a data line(delete)
  37. """
  38. # authentication_classes = [] # 禁用所有认证类
  39. # permission_classes = [AllowAny] # 允许任意访问
  40. pagination_class = MyPageNumberPagination
  41. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  42. ordering_fields = ['id', "create_time", "update_time", ]
  43. filter_class = OutBoundDemandFilter
  44. def get_project(self):
  45. try:
  46. id = self.kwargs.get('pk')
  47. return id
  48. except:
  49. return None
  50. def get_queryset(self):
  51. id = self.get_project()
  52. if self.request.user:
  53. if id is None:
  54. return OutBoundDemandModel.objects.filter( is_delete=False)
  55. else:
  56. return OutBoundDemandModel.objects.filter( id=id, is_delete=False)
  57. else:
  58. return OutBoundDemandModel.objects.none()
  59. def get_serializer_class(self):
  60. if self.action in ['list' ]:
  61. return OutBoundDemandModelSerializer
  62. else:
  63. return OutBoundDemandModelSerializer
  64. def batch_list(self, request):
  65. data =self.request.data
  66. OutBoundDemand_all = OutBoundDemandModel.objects.filter(bound_list_id=data['bound_list_id'], is_delete=False).all()
  67. data = OutBoundDemandModelSerializer(OutBoundDemand_all, many=True).data
  68. return_data ={
  69. "code": 200,
  70. "msg": "Success Create",
  71. "data": data
  72. }
  73. return Response(return_data,status=200,headers={})
  74. def create(self, request, *args, **kwargs):
  75. data = self.request.data
  76. data['openid'] = self.request.auth.openid
  77. data['create_time'] = str(timezone.now().strftime('%Y-%m-%d %H:%M:%S'))
  78. data['update_time'] = str(timezone.now().strftime('%Y-%m-%d %H:%M:%S'))
  79. data['working'] = True
  80. bound_list_obj = BoundListModel.objects.get(id=data['bound_list_id'])
  81. OutBoundDemand_obj =OutBoundDemandModel.objects.create(
  82. bound_list=bound_list_obj,
  83. goods_code=data['goods_code'],
  84. goods_desc=data['goods_desc'],
  85. goods_std=data['goods_std'],
  86. goods_unit=data['goods_unit'],
  87. goods_qty=data['goods_out_qty'],
  88. out_type = data['out_type'],
  89. creater=data['creater'],
  90. create_time=data['create_time'],
  91. update_time=data['update_time'],
  92. working=data['working']
  93. )
  94. return_data = OutBoundDemandModelSerializer(OutBoundDemand_obj).data
  95. headers = self.get_success_headers(return_data)
  96. return Response(return_data, status=200, headers=headers)
  97. def batch_demanded_list(self, request):
  98. data =self.request.data
  99. OutBoundDemand_all = OutBatchModel.objects.filter(bound_list_id=data['bound_list_id'], is_delete=False).all()
  100. data = OutBatchGetSerializer(OutBoundDemand_all, many=True).data
  101. return_data = {
  102. "code": 200,
  103. "msg": "Success Create",
  104. "data": data
  105. }
  106. return Response(return_data,status=200,headers={})
  107. def distribute(self, request):
  108. """主分配入口"""
  109. try:
  110. with transaction.atomic():
  111. bound_list_id, demands,out_type, creater= self.validate_distribute_request(request)
  112. if OutBatchModel.objects.filter(bound_list_id=bound_list_id, is_delete=False).exists():
  113. return_data = {
  114. "code": 200,
  115. "msg": "Success Create",
  116. "data": {
  117. "msg": "该订单已分配,请勿重复分配"
  118. }
  119. }
  120. return Response(return_data, status=200, headers={})
  121. aggregated_demands = self.aggregate_demands(demands)
  122. result = self.process_all_goods(aggregated_demands, request, out_type, creater,bound_list_id)
  123. return self.build_success_response(result)
  124. except APIException as e:
  125. return self.build_error_response(e.detail, 200)
  126. except Exception as e:
  127. return self.build_error_response(str(e), 200)
  128. # 验证层方法
  129. def validate_distribute_request(self, request):
  130. """验证请求参数"""
  131. bound_list_id = request.data.get('bound_list_id')
  132. if not bound_list_id:
  133. raise APIException({"detail": "Missing bound_list_id"})
  134. demands = OutBoundDemandModel.objects.filter(
  135. bound_list_id=bound_list_id,
  136. is_delete=False
  137. )
  138. if not demands.exists():
  139. raise APIException({"detail": "No demands found"})
  140. base_info = OutBoundDemandModel.objects.filter(
  141. bound_list_id=bound_list_id,
  142. is_delete=False
  143. ).first()
  144. out_type = base_info.out_type
  145. creater = base_info.creater
  146. return bound_list_id, demands, out_type, creater
  147. # 数据处理层方法
  148. def aggregate_demands(self, demands):
  149. """合并相同物料需求"""
  150. return demands.values('goods_code').annotate(
  151. total_demand=Sum('goods_qty')
  152. )
  153. # 核心分配逻辑
  154. def process_all_goods(self, aggregated_demands, request, out_type, creater,bound_list_id):
  155. """处理所有物料分配"""
  156. return [
  157. self.process_single_goods(
  158. goods_code=item['goods_code'],
  159. total_demand=item['total_demand'],
  160. request=request,
  161. out_type=out_type,
  162. creater=creater,
  163. bound_list_id=bound_list_id
  164. )
  165. for item in aggregated_demands
  166. ]
  167. def process_single_goods(self, goods_code, total_demand, request,out_type, creater,bound_list_id):
  168. """处理单个物料分配"""
  169. batches = self.get_available_batches(goods_code)
  170. remaining, allocations = self.allocate_batches(total_demand, batches, request,out_type, creater,bound_list_id)
  171. if remaining > 0:
  172. raise APIException({
  173. "detail": f"Insufficient stock for {goods_code}",
  174. "required": total_demand,
  175. "allocated": total_demand - remaining
  176. })
  177. return {
  178. "goods_code": goods_code,
  179. "total_demand": total_demand,
  180. "allocations": allocations
  181. }
  182. def get_available_batches(self, goods_code):
  183. """获取可用入库批次"""
  184. return BoundBatchModel.objects.filter(
  185. goods_code=goods_code,
  186. is_delete=False
  187. ).order_by('bound_batch_order')
  188. # 批次分配逻辑
  189. def allocate_batches(self, total_demand, batches, request,out_type, creater,bound_list_id):
  190. """分配具体批次"""
  191. remaining = total_demand
  192. allocations = []
  193. for batch in batches:
  194. if remaining <= 0:
  195. break
  196. allocated = self.allocate_single_batch(
  197. batch=batch,
  198. remaining=remaining,
  199. request=request,
  200. out_type=out_type,
  201. creater=creater,
  202. bound_list_id=bound_list_id
  203. )
  204. if allocated == 0:
  205. continue
  206. allocations.append(allocated)
  207. remaining -= allocated['allocated']
  208. return remaining, allocations
  209. def allocate_single_batch(self, batch, remaining, request,out_type, creater,bound_list_id):
  210. """单个批次分配逻辑"""
  211. available = batch.goods_in_location_qty - batch.goods_out_qty
  212. if available <= 0:
  213. return 0
  214. allocate_qty = min(remaining, available)
  215. self.update_batch_status(batch, allocate_qty)
  216. out_batch = self.create_out_batch(batch, allocate_qty, request,out_type, creater,bound_list_id)
  217. return {
  218. "batch": batch.bound_number,
  219. "allocated": allocate_qty,
  220. "out_batch": out_batch.out_number
  221. }
  222. # 数据操作层方法
  223. def update_batch_status(self, batch, allocate_qty):
  224. """更新批次状态和数量"""
  225. batch.goods_out_qty += allocate_qty
  226. if batch.goods_out_qty == batch.goods_in_location_qty:
  227. batch.status = 6 # 已出库
  228. elif batch.goods_out_qty > 0:
  229. batch.status = 5 # 部分出库
  230. batch.save()
  231. def create_out_batch(self, batch, allocate_qty, request,out_type, creater,bound_list_id):
  232. """创建出库记录"""
  233. out_data = {
  234. 'bound_list': bound_list_id,
  235. 'out_number': self.generate_out_number(batch.goods_code),
  236. 'batch_number': batch.id,
  237. 'out_date': timezone.now(),
  238. 'warehouse_code': batch.warehouse_code,
  239. 'warehouse_name': batch.warehouse_name,
  240. 'goods_code': batch.goods_code,
  241. 'goods_desc': batch.goods_desc,
  242. 'goods_out_qty': allocate_qty,
  243. 'status': 0,
  244. 'openid': request.auth.openid,
  245. 'out_type': out_type,
  246. 'creater': creater,
  247. }
  248. serializer = OutBatchPostSerializer(data=out_data)
  249. if not serializer.is_valid():
  250. raise APIException({
  251. "detail": f"Serialization error for {batch.goods_code}",
  252. "errors": serializer.errors
  253. })
  254. return serializer.save()
  255. # 工具方法
  256. def generate_out_number(self, goods_code):
  257. """生成唯一出库单号"""
  258. timestamp = timezone.now().strftime("%Y%m%d%H%M%S")
  259. import uuid
  260. return f"OUT-{goods_code}-{timestamp}-{uuid.uuid4().hex[:6]}"
  261. def build_success_response(self, data):
  262. """构建成功响应"""
  263. return Response({
  264. "code": 200,
  265. "msg": "Distribution completed",
  266. "data": data
  267. })
  268. def build_error_response(self, error, status_code):
  269. """构建错误响应"""
  270. return Response({
  271. "code": status_code,
  272. "msg": "Distribution failed" if status_code == 400 else "Server error",
  273. "error": error
  274. }, status=status_code)
  275. class MaterialStatisticsViewSet(viewsets.ModelViewSet):
  276. """
  277. retrieve:
  278. Response a data list(get)
  279. list:
  280. Response a data list(all)
  281. create:
  282. Create a data line(post)
  283. delete:
  284. Delete a data line(delete)
  285. """
  286. # authentication_classes = [] # 禁用所有认证类
  287. # permission_classes = [AllowAny] # 允许任意访问
  288. pagination_class = MyPageNumberPagination
  289. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  290. ordering_fields = ['id', "create_time", "update_time", ]
  291. filter_class = MaterialStatisticsFilter
  292. def get_project(self):
  293. try:
  294. id = self.kwargs.get('pk')
  295. return id
  296. except:
  297. return None
  298. def get_queryset(self):
  299. id = self.get_project()
  300. if self.request.user:
  301. if id is None:
  302. return MaterialStatistics.objects.filter()
  303. else:
  304. return MaterialStatistics.objects.filter(id=id)
  305. else:
  306. return MaterialStatistics.objects.none()
  307. def get_serializer_class(self):
  308. if self.action in ['list' ]:
  309. return MaterialStatisticsSerializer
  310. elif self.action in ['retrieve']:
  311. return MaterialStatisticsSerializer_items
  312. else:
  313. return self.http_method_not_allowed(request=self.request)
  314. class BoundListViewSet(viewsets.ModelViewSet):
  315. """
  316. retrieve:
  317. Response a data list(get)
  318. list:
  319. Response a data list(all)
  320. create:
  321. Create a data line(post)
  322. delete:
  323. Delete a data line(delete)
  324. """
  325. # authentication_classes = [] # 禁用所有认证类
  326. # permission_classes = [AllowAny] # 允许任意访问
  327. pagination_class = MyPageNumberPagination
  328. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  329. ordering_fields = ['id', "create_time", "update_time", ]
  330. filter_class = BoundListFilter
  331. def get_project(self):
  332. try:
  333. id = self.kwargs.get('pk')
  334. return id
  335. except:
  336. return None
  337. def get_queryset(self):
  338. id = self.get_project()
  339. if self.request.user:
  340. if id is None:
  341. return BoundListModel.objects.filter( is_delete=False)
  342. else:
  343. return BoundListModel.objects.filter( id=id, is_delete=False)
  344. else:
  345. return BoundListModel.objects.none()
  346. def get_serializer_class(self):
  347. if self.action in ['list', 'destroy','retrieve']:
  348. return BoundListGetSerializer
  349. elif self.action in ['create', 'update']:
  350. return BoundListPostSerializer
  351. else:
  352. return self.http_method_not_allowed(request=self.request)
  353. def create(self, request, *args, **kwargs):
  354. data = self.request.data
  355. # if BoundListModel.objects.filter(code=data['code'], is_delete=False).exists():
  356. # raise APIException({"detail": "Data exists"})
  357. # else:
  358. data['openid'] = self.request.auth.openid
  359. data['bound_date'] =str(timezone.now().strftime('%Y-%m-%d'))
  360. order_day=str(timezone.now().strftime('%Y-%m-'))
  361. data['bound_month'] =str(timezone.now().strftime('%Y%m'))
  362. if data['bound_type'] == 'in':
  363. data['bound_status'] = '100'
  364. else:
  365. data['bound_status'] = '200'
  366. qs_set = BoundListModel.objects.filter(bound_month=data['bound_month'], bound_code_type=data['bound_code_type'], is_delete=False)
  367. print('qs_set是:', len(qs_set))
  368. if len(qs_set) > 0:
  369. bound_last_code = qs_set.order_by('-id').first().bound_code
  370. data['bound_code'] = data['bound_code_type'] +'-'+ order_day + str(int(bound_last_code.split('-')[-1])+1).zfill(4)
  371. else:
  372. data['bound_code'] = data['bound_code_type'] +'-'+ order_day + '0001'
  373. serializer = self.get_serializer(data=data)
  374. serializer.is_valid(raise_exception=True)
  375. serializer.save()
  376. headers = self.get_success_headers(serializer.data)
  377. return Response(serializer.data, status=200, headers=headers)
  378. def update(self, request, pk):
  379. qs = self.get_object()
  380. data = self.request.data
  381. serializer = self.get_serializer(qs, data=data)
  382. serializer.is_valid(raise_exception=True)
  383. serializer.save()
  384. headers = self.get_success_headers(serializer.data)
  385. return Response(serializer.data, status=200, headers=headers)
  386. def destroy(self, request, pk):
  387. qs = self.get_object()
  388. if qs.openid != self.request.auth.openid:
  389. raise APIException({"detail": "该入库非您所属,禁止删除,您可以进行编辑"})
  390. else:
  391. qs.is_delete = True
  392. qs.bound_code =qs.bound_code+'-delete'+str(timezone.now().strftime('%Y%m%d%H%M%S'))
  393. qs.save()
  394. serializer = self.get_serializer(qs, many=False)
  395. headers = self.get_success_headers(serializer.data)
  396. return Response(serializer.data, status=200, headers=headers)
  397. class BoundBatchViewSet(viewsets.ModelViewSet):
  398. """
  399. retrieve:
  400. Response a data list(get)
  401. list:
  402. Response a data list(all)
  403. create:
  404. Create a data line(post)
  405. delete:
  406. Delete a data line(delete)
  407. """
  408. # authentication_classes = [] # 禁用所有认证类
  409. # permission_classes = [AllowAny] # 允许任意访问
  410. pagination_class = MyPageNumberPagination
  411. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  412. ordering_fields = ['id', "create_time", "update_time", ]
  413. filter_class = BoundBatchFilter
  414. def get_project(self):
  415. try:
  416. id = self.kwargs.get('pk')
  417. return id
  418. except:
  419. return None
  420. def get_queryset(self):
  421. id = self.get_project()
  422. if self.request.user:
  423. if id is None:
  424. return BoundBatchModel.objects.filter( is_delete=False)
  425. else:
  426. return BoundBatchModel.objects.filter( id=id, is_delete=False)
  427. else:
  428. return BoundBatchModel.objects.none()
  429. def get_serializer_class(self):
  430. if self.action in ['list', 'destroy','retrieve']:
  431. return BoundBatchGetSerializer
  432. elif self.action in ['create', 'update']:
  433. return BoundBatchPostSerializer
  434. else:
  435. return self.http_method_not_allowed(request=self.request)
  436. def create(self, request, *args, **kwargs):
  437. data = self.request.data
  438. try:
  439. data['openid'] = self.request.auth.openid
  440. data['goods_total_weight'] = data['goods_weight']*data['goods_qty']
  441. order_day=str(timezone.now().strftime('-%Y%m'))
  442. order_month=str(timezone.now().strftime('%Y%m'))
  443. data['bound_month'] =str(timezone.now().strftime('%Y%m'))
  444. print(data['order'])
  445. if data['order'] == 'true':
  446. qs_set = BoundBatchModel.objects.filter( goods_code=data['goods_code'], bound_month=order_month, is_delete=False)
  447. print('qs_set是:', len(qs_set))
  448. if len(qs_set) > 0:
  449. bound_last_code = qs_set.order_by('-bound_batch_order').first().bound_number
  450. data['bound_batch_order'] = int(bound_last_code.split('-')[-1])+1
  451. data['bound_number'] = data['goods_code'] + '-' + str(int(bound_last_code.split('-')[-1])+1)
  452. else:
  453. data['bound_batch_order'] = int(order_day.split('-')[-1])*1000 +1
  454. data['bound_number'] = data['goods_code'] + order_day + '001'
  455. else:
  456. data['bound_number'] = data['goods_code'] + '-' + str(data['bound_batch_order'])
  457. serializer = self.get_serializer(data=data)
  458. serializer.is_valid(raise_exception=True)
  459. serializer.save()
  460. headers = self.get_success_headers(serializer.data)
  461. self.add_batch_log(serializer.data, 0, data['goods_qty'])
  462. return Response(serializer.data, status=200, headers=headers)
  463. except Exception as e:
  464. print(e)
  465. raise APIException({"detail": "{}".format(e)})
  466. def add_batch_log(self, data, log_type, goods_qty):
  467. choices_dict = dict(BatchLogModel.BATCH_LOG_TYPE)
  468. log_type_name = choices_dict.get(log_type, "未知类型")
  469. try:
  470. # 获取 BoundBatchModel 实例
  471. batch_obj = BoundBatchModel.objects.get(id=data['id'])
  472. except BoundBatchModel.DoesNotExist:
  473. return False
  474. log_data = {
  475. 'batch_id': batch_obj,
  476. 'log_type': log_type,
  477. 'log_date': timezone.now().strftime('%Y-%m-%d-%H:%M'), # 直接格式化时间,无需转字符串
  478. 'goods_code': data['goods_code'],
  479. 'goods_desc': data['goods_desc'],
  480. 'goods_qty': data['goods_qty'],
  481. 'log_content': f"{log_type_name} {data['goods_qty']}件",
  482. 'creater': data['creater'],
  483. 'openid': data['openid'],
  484. 'is_delete': False,
  485. }
  486. # 创建日志记录
  487. BatchLogModel.objects.create(**log_data)
  488. return True
  489. def update(self, request, pk):
  490. qs = self.get_object()
  491. data = self.request.data
  492. serializer = self.get_serializer(qs, data=data)
  493. serializer.is_valid(raise_exception=True)
  494. serializer.save()
  495. headers = self.get_success_headers(serializer.data)
  496. return Response(serializer.data, status=200, headers=headers)
  497. def destroy(self, request, pk):
  498. qs = self.get_object()
  499. if qs.openid != self.request.auth.openid:
  500. raise APIException({"detail": "该入库非您所属,禁止删除,您可以进行编辑"})
  501. else:
  502. qs.is_delete = True
  503. qs.save()
  504. serializer = self.get_serializer(qs, many=False)
  505. headers = self.get_success_headers(serializer.data)
  506. return Response(serializer.data, status=200, headers=headers)
  507. class BoundDetailViewSet(viewsets.ModelViewSet):
  508. """
  509. retrieve:
  510. Response a data list(get)
  511. list:
  512. Response a data list(all)
  513. create:
  514. Create a data line(post)
  515. delete:
  516. Delete a data line(delete)
  517. """
  518. # authentication_classes = [] # 禁用所有认证类
  519. # permission_classes = [AllowAny] # 允许任意访问
  520. pagination_class = MyPageNumberPagination
  521. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  522. ordering_fields = ['id', "create_time", "update_time", ]
  523. filter_class = BoundDetailFilter
  524. def get_project(self):
  525. try:
  526. id = self.kwargs.get('pk')
  527. return id
  528. except:
  529. return None
  530. def get_queryset(self):
  531. id = self.get_project()
  532. if self.request.user:
  533. if id is None:
  534. return BoundDetailModel.objects.filter( is_delete=False)
  535. else:
  536. return BoundDetailModel.objects.filter( id=id, is_delete=False)
  537. else:
  538. return BoundDetailModel.objects.none()
  539. def get_serializer_class(self):
  540. if self.action in ['list', 'destroy','retrieve']:
  541. return BoundDetailGetSerializer
  542. elif self.action in ['create', 'update']:
  543. return BoundDetailPostSerializer
  544. else:
  545. return self.http_method_not_allowed(request=self.request)
  546. def create(self, request, *args, **kwargs):
  547. data = self.request.data
  548. data['openid'] = self.request.auth.openid
  549. data.setdefault('is_delete', False)
  550. # 验证并保存数据
  551. data['detail_code'] = f"DC-{data['bound_list']:02}{data['bound_batch']:02}"
  552. print(data['detail_code'])
  553. if BoundDetailModel.objects.filter(detail_code=data['detail_code'], is_delete=False).exists():
  554. raise APIException({"detail": "Data exists"})
  555. else:
  556. serializer = self.get_serializer(data=data)
  557. serializer.is_valid(raise_exception=True)
  558. serializer.save()
  559. # 返回响应
  560. headers = self.get_success_headers(serializer.data)
  561. return Response(serializer.data, status=200, headers=headers)
  562. def update(self, request, pk):
  563. qs = self.get_object()
  564. data = self.request.data
  565. serializer = self.get_serializer(qs, data=data)
  566. serializer.is_valid(raise_exception=True)
  567. serializer.save()
  568. headers = self.get_success_headers(serializer.data)
  569. return Response(serializer.data, status=200, headers=headers)
  570. def destroy(self, request, pk):
  571. qs = self.get_object()
  572. if qs.openid != self.request.auth.openid:
  573. raise APIException({"detail": "该入库非您所属,禁止删除,您可以进行编辑"})
  574. else:
  575. qs.is_delete = True
  576. qs.save()
  577. serializer = self.get_serializer(qs, many=False)
  578. headers = self.get_success_headers(serializer.data)
  579. return Response(serializer.data, status=200, headers=headers)
  580. class OutBoundDetailViewSet(viewsets.ModelViewSet):
  581. """
  582. retrieve:
  583. Response a data list(get)
  584. list: Response a data list(all)
  585. create:
  586. Create a data line(post)
  587. delete:
  588. Delete a data line(delete)
  589. """
  590. # authentication_classes = [] # 禁用所有认证类
  591. # permission_classes = [AllowAny] # 允许任意访问
  592. pagination_class = MyPageNumberPagination
  593. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  594. ordering_fields = ['id', "create_time", "update_time", ]
  595. filter_class = OutBoundDetailFilter
  596. def get_project(self):
  597. try:
  598. id = self.kwargs.get('pk')
  599. return id
  600. except:
  601. return None
  602. def get_queryset(self):
  603. id = self.get_project()
  604. if self.request.user:
  605. if id is None:
  606. return OutBoundDetailModel.objects.filter( is_delete=False)
  607. else:
  608. return OutBoundDetailModel.objects.filter( id=id, is_delete=False)
  609. else:
  610. return OutBoundDetailModel.objects.none()
  611. def get_serializer_class(self):
  612. if self.action in ['list', 'destroy','retrieve']:
  613. return OutBoundDetailGetSerializer
  614. elif self.action in ['create', 'update']:
  615. return OutBoundDetailPostSerializer
  616. else:
  617. return self.http_method_not_allowed(request=self.request)
  618. def create(self, request, *args, **kwargs):
  619. data = self.request.data
  620. data['openid'] = self.request.auth.openid
  621. data.setdefault('is_delete', False)
  622. data['bound_batch_number'] = OutBatchModel.objects.get(id=data['bound_batch']).batch_number.id
  623. # 验证并保存数据
  624. data['detail_code'] = f"DC-{data['bound_list']:02}{data['bound_batch']:02}"
  625. print(data['detail_code'])
  626. if OutBoundDetailModel.objects.filter(detail_code=data['detail_code'], is_delete=False).exists():
  627. raise APIException({"detail": "Data exists"})
  628. else:
  629. serializer = self.get_serializer(data=data)
  630. serializer.is_valid(raise_exception=True)
  631. serializer.save()
  632. # 返回响应
  633. headers = self.get_success_headers(serializer.data)
  634. return Response(serializer.data, status=200, headers=headers)
  635. def update(self, request, pk):
  636. qs = self.get_object()
  637. data = self.request.data
  638. serializer = self.get_serializer(qs, data=data)
  639. serializer.is_valid(raise_exception=True)
  640. serializer.save()
  641. headers = self.get_success_headers(serializer.data)
  642. return Response(serializer.data, status=200, headers=headers)
  643. def destroy(self, request, pk):
  644. qs = self.get_object()
  645. if qs.openid != self.request.auth.openid:
  646. raise APIException({"detail": "该入库非您所属,禁止删除,您可以进行编辑"})
  647. else:
  648. qs.is_delete = True
  649. qs.save()
  650. serializer = self.get_serializer(qs, many=False)
  651. headers = self.get_success_headers(serializer.data)
  652. return Response(serializer.data, status=200, headers=headers)
  653. class OutBoundBatchViewSet(viewsets.ModelViewSet):
  654. """
  655. retrieve:
  656. Response a data list(get)
  657. list:
  658. Response a data list(all)
  659. create:
  660. Create a data line(post)
  661. delete:
  662. Delete a data line(delete)
  663. """
  664. # authentication_classes = [] # 禁用所有认证类
  665. # permission_classes = [AllowAny] # 允许任意访问
  666. pagination_class = MyPageNumberPagination
  667. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  668. ordering_fields = ['id', "create_time", "update_time", ]
  669. filter_class = OutBatchFilter
  670. def get_project(self):
  671. try:
  672. id = self.kwargs.get('pk')
  673. return id
  674. except:
  675. return None
  676. def get_queryset(self):
  677. id = self.get_project()
  678. if self.request.user:
  679. if id is None:
  680. return OutBatchModel.objects.filter( is_delete=False)
  681. else:
  682. return OutBatchModel.objects.filter( id=id, is_delete=False)
  683. else:
  684. return OutBatchModel.objects.none()
  685. def get_serializer_class(self):
  686. if self.action in ['list', 'destroy','retrieve']:
  687. return OutBatchGetSerializer
  688. elif self.action in ['create', 'update']:
  689. return OutBatchPostSerializer
  690. else:
  691. return self.http_method_not_allowed(request=self.request)
  692. def create(self, request, *args, **kwargs):
  693. data = self.request.data
  694. batch_obj = BoundBatchModel.objects.get(bound_number=data['out_number'])
  695. if batch_obj is None:
  696. raise APIException({"detail": "批次不存在"})
  697. data['batch_number'] = batch_obj.id
  698. data['out_date'] = str(timezone.now().strftime('%Y-%m-%d %H:%M:%S'))
  699. data['openid'] = self.request.auth.openid
  700. data.setdefault('is_delete', False)
  701. data['goods_total_weight'] = data['goods_weight']*data['goods_out_qty']
  702. data['goods_qty'] = batch_obj.goods_qty -batch_obj.goods_out_qty - data['goods_out_qty']
  703. batch_obj.goods_out_qty += data['goods_out_qty']
  704. if batch_obj.goods_out_qty > batch_obj.goods_in_qty:
  705. raise APIException({"detail": "出库数量大于批次数量"})
  706. batch_obj.goods_qty -= data['goods_out_qty']
  707. batch_obj.save()
  708. data['status'] = 0 #现在处于出库申请状态
  709. serializer = self.get_serializer(data=data)
  710. serializer.is_valid(raise_exception=True)
  711. serializer.save()
  712. headers = self.get_success_headers(serializer.data)
  713. self.add_batch_log(serializer.data, 1, data['goods_out_qty'])
  714. return Response(serializer.data, status=200, headers=headers)
  715. def update(self, request, pk):
  716. qs = self.get_object()
  717. data = self.request.data
  718. data['openid'] = self.request.auth.openid
  719. data.setdefault('is_delete', False)
  720. data['goods_total_weight'] = data['goods_weight']*data['goods_qty']
  721. serializer = self.get_serializer(qs, data=data)
  722. serializer.is_valid(raise_exception=True)
  723. serializer.save()
  724. headers = self.get_success_headers(serializer.data)
  725. self.add_batch_log(serializer.data, 1, data['goods_qty'])
  726. return Response(serializer.data, status=200, headers=headers)
  727. def destroy(self, request, pk):
  728. qs = self.get_object()
  729. if qs.openid != self.request.auth.openid:
  730. raise APIException({"detail": "该出库非您所属,禁止删除,您可以进行编辑"})
  731. else:
  732. qs.is_delete = True
  733. qs.save()
  734. serializer = self.get_serializer(qs, many=False)
  735. headers = self.get_success_headers(serializer.data)
  736. return Response(serializer.data, status=200, headers=headers)
  737. def add_batch_log(self, data, log_type, goods_qty):
  738. choices_dict = dict(BatchLogModel.BATCH_LOG_TYPE)
  739. log_type_name = choices_dict.get(log_type, "未知类型")
  740. try:
  741. # 获取 BoundBatchModel 实例
  742. batch_obj = BoundBatchModel.objects.get(id=data['batch_number'])
  743. except BoundBatchModel.DoesNotExist:
  744. # 处理批次不存在的情况(如记录日志或抛出异常)
  745. return False
  746. log_data = {
  747. 'batch_id': batch_obj, # 关键修复:传入实例对象,而不是 batch_obj.id
  748. 'log_type': log_type,
  749. 'log_date': timezone.now().strftime('%Y-%m-%d-%H:%M'), # 直接格式化时间,无需转字符串
  750. 'goods_code': data['goods_code'],
  751. 'goods_desc': data['goods_desc'],
  752. 'goods_qty': data['goods_qty'],
  753. 'log_content': f"{log_type_name} {goods_qty}件",
  754. 'creater': data['creater'],
  755. 'openid': data['openid'],
  756. 'is_delete': False,
  757. # 注意:create_time 和 update_time 由模型的 auto_now_add 和 auto_now 自动处理,无需手动赋值
  758. }
  759. # 创建日志记录
  760. BatchLogModel.objects.create(**log_data)
  761. return True
  762. class BoundBatchLogViewSet(viewsets.ModelViewSet):
  763. """
  764. retrieve:
  765. Response a data list(get)
  766. list:
  767. Response a data list(all)
  768. delete:
  769. Delete a data line(delete)
  770. """
  771. # authentication_classes = [] # 禁用所有认证类
  772. # permission_classes = [AllowAny] # 允许任意访问
  773. pagination_class = MyPageNumberPagination
  774. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  775. ordering_fields = ['id', "create_time", "update_time", ]
  776. filter_class = BatchlogFilter
  777. def get_queryset(self):
  778. return BatchLogModel.objects.filter( is_delete=False)
  779. def get_serializer_class(self):
  780. if self.action in ['list', 'destroy','retrieve']:
  781. return BatchLogGetSerializer
  782. else:
  783. return self.http_method_not_allowed(request=self.request)
  784. def destroy(self, request, pk):
  785. qs = self.get_object()
  786. qs.is_delete = True
  787. qs.save()
  788. serializer = self.get_serializer(qs, many=False)
  789. headers = self.get_success_headers(serializer.data)
  790. return Response(serializer.data, status=200, headers=headers)
  791. class BatchContainerAPIView(APIView):
  792. """
  793. post:
  794. 返回批次对应的container列表
  795. """
  796. # authentication_classes = [] # 禁用所有认证类
  797. # permission_classes = [AllowAny] # 允许任意访问
  798. def post(self, request):
  799. data = request.data
  800. batch_id = data.get('batch_id')
  801. from container.models import ContainerDetailModel
  802. container_detail_all = ContainerDetailModel.objects.filter(batch_id=batch_id, is_delete=False).exclude(status=3).all()
  803. container_dict = {}
  804. for container_detail in container_detail_all:
  805. container_id = container_detail.container_id
  806. if container_id not in container_dict:
  807. container_dict[container_id] = {
  808. 'id': container_id,
  809. 'goods_code': container_detail.goods_code,
  810. 'goods_desc': container_detail.goods_desc,
  811. 'container_code': container_detail.container.container_code,
  812. 'current_location': container_detail.container.current_location,
  813. 'goods_qty': container_detail.goods_qty-container_detail.goods_out_qty,
  814. 'class':container_detail.goods_class
  815. }
  816. else:
  817. container_dict[container_id]['goods_qty'] += container_detail.goods_qty-container_detail.goods_out_qty
  818. container_dict = list(container_dict.values())
  819. return_data = {'code': 200,'msg': 'Success Create', 'data': container_dict}
  820. return Response(return_data)