views.py 61 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495
  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 django.http import StreamingHttpResponse
  13. from rest_framework.settings import api_settings
  14. from .models import BoundListModel, BoundDetailModel,BoundBatchModel, BatchOperateLogModel, OutBatchModel,OutBoundDetailModel,MaterialStatistics,OutBoundDemandModel
  15. from .files import BatchFileRenderCN
  16. from .serializers import BoundListGetSerializer,BoundListPostSerializer,BoundBatchGetSerializer,BoundBatchPostSerializer,BoundDetailGetSerializer,BoundDetailPostSerializer
  17. from .serializers import OutBoundDetailGetSerializer,OutBoundDetailPostSerializer,OutBatchGetSerializer,OutBatchPostSerializer,BatchLogGetSerializer
  18. from .serializers import MaterialStatisticsSerializer,MaterialStatisticsSerializer_items
  19. from .serializers import OutBoundDemandModelSerializer
  20. from .filter import BoundListFilter, BoundDetailFilter,BoundBatchFilter
  21. from .filter import OutBatchFilter,OutBoundDetailFilter,BatchlogFilter
  22. from .filter import MaterialStatisticsFilter
  23. from .filter import OutBoundDemandFilter
  24. # 以后添加模块检验
  25. from warehouse.models import ListModel as warehouse
  26. from staff.models import ListModel as staff
  27. from rest_framework.permissions import AllowAny
  28. from rest_framework.views import APIView
  29. from operation_log.views import log_operation,log_failure_operation,log_success_operation
  30. from django.db.models import F
  31. # 出库需求视图类
  32. class OutBoundDemandViewSet(viewsets.ModelViewSet):
  33. """
  34. retrieve:
  35. Response a data list(get)
  36. list:
  37. Response a data list(all)
  38. create:
  39. Create a data line(post)
  40. delete:
  41. Delete a data line(delete)
  42. """
  43. # authentication_classes = [] # 禁用所有认证类
  44. # permission_classes = [AllowAny] # 允许任意访问
  45. pagination_class = MyPageNumberPagination
  46. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  47. ordering_fields = ['id', "create_time", "update_time", ]
  48. filter_class = OutBoundDemandFilter
  49. def get_project(self):
  50. try:
  51. id = self.kwargs.get('pk')
  52. return id
  53. except:
  54. return None
  55. def get_queryset(self):
  56. id = self.get_project()
  57. if self.request.user:
  58. if id is None:
  59. return OutBoundDemandModel.objects.filter( is_delete=False)
  60. else:
  61. return OutBoundDemandModel.objects.filter( id=id, is_delete=False)
  62. else:
  63. return OutBoundDemandModel.objects.none()
  64. def get_serializer_class(self):
  65. if self.action in ['list' ]:
  66. return OutBoundDemandModelSerializer
  67. else:
  68. return OutBoundDemandModelSerializer
  69. def batch_list(self, request):
  70. data =self.request.data
  71. OutBoundDemand_all = OutBoundDemandModel.objects.filter(bound_list_id=data['bound_list_id'], is_delete=False).all()
  72. data = OutBoundDemandModelSerializer(OutBoundDemand_all, many=True).data
  73. return_data ={
  74. "code": 200,
  75. "msg": "Success Create",
  76. "data": data
  77. }
  78. log_operation(
  79. request=self.request,
  80. operation_content=f"查询出库需求列表,出库单ID:{data.get('bound_list_id')}",
  81. operation_level="view",
  82. operator=self.request.auth.name if self.request.auth else None,
  83. module_name="出库需求"
  84. )
  85. return Response(return_data,status=200,headers={})
  86. def create(self, request, *args, **kwargs):
  87. data = self.request.data
  88. try:
  89. data['openid'] = self.request.auth.openid
  90. data['create_time'] = str(timezone.now().strftime('%Y-%m-%d %H:%M:%S'))
  91. data['update_time'] = str(timezone.now().strftime('%Y-%m-%d %H:%M:%S'))
  92. data['working'] = True
  93. bound_list_obj = BoundListModel.objects.get(id=data['bound_list_id'])
  94. OutBoundDemand_obj =OutBoundDemandModel.objects.create(
  95. bound_list=bound_list_obj,
  96. goods_code=data['goods_code'],
  97. goods_desc=data['goods_desc'],
  98. goods_std=data['goods_std'],
  99. goods_unit=data['goods_unit'],
  100. goods_qty=data['goods_out_qty'],
  101. out_type = data['out_type'],
  102. creater=data['creater'],
  103. create_time=data['create_time'],
  104. update_time=data['update_time'],
  105. working=data['working']
  106. )
  107. return_data = OutBoundDemandModelSerializer(OutBoundDemand_obj).data
  108. headers = self.get_success_headers(return_data)
  109. log_success_operation(
  110. request=self.request,
  111. operation_content=f"创建出库需求 ID:{OutBoundDemand_obj.id},创建内容:{data}",
  112. operation_level="new",
  113. operator=self.request.auth.name if self.request.auth else None,
  114. object_id=OutBoundDemand_obj.id,
  115. module_name="出库需求"
  116. )
  117. return Response(return_data, status=200, headers=headers)
  118. except Exception as e:
  119. log_failure_operation(
  120. request=self.request,
  121. operation_content=f"创建出库需求失败: {str(e)}, 创建内容:{data}",
  122. operation_level="new",
  123. operator=self.request.auth.name if self.request.auth else None,
  124. module_name="出库需求"
  125. )
  126. raise
  127. def batch_demanded_list(self, request):
  128. data =self.request.data
  129. OutBoundDemand_all = OutBatchModel.objects.filter(bound_list_id=data['bound_list_id'], is_delete=False).all()
  130. data = OutBatchGetSerializer(OutBoundDemand_all, many=True).data
  131. return_data = {
  132. "code": 200,
  133. "msg": "Success Create",
  134. "data": data
  135. }
  136. log_operation(
  137. request=self.request,
  138. operation_content=f"查询出库批次列表,出库单ID:{data.get('bound_list_id')}",
  139. operation_level="view",
  140. operator=self.request.auth.name if self.request.auth else None,
  141. module_name="出库批次"
  142. )
  143. return Response(return_data,status=200,headers={})
  144. def distribute(self, request):
  145. """主分配入口"""
  146. try:
  147. with transaction.atomic():
  148. bound_list_id, demands,out_type, creater= self.validate_distribute_request(request)
  149. if OutBatchModel.objects.filter(bound_list_id=bound_list_id, is_delete=False).exists():
  150. log_operation(
  151. request=self.request,
  152. operation_content=f"出库分配失败,出库单ID:{bound_list_id} 已分配,请勿重复分配",
  153. operation_level="other",
  154. operator=self.request.auth.name if self.request.auth else None,
  155. module_name="出库分配"
  156. )
  157. return_data = {
  158. "code": 200,
  159. "msg": "Success Create",
  160. "data": {
  161. "msg": "该订单已分配,请勿重复分配"
  162. }
  163. }
  164. return Response(return_data, status=200, headers={})
  165. aggregated_demands = self.aggregate_demands(demands)
  166. result = self.process_all_goods(aggregated_demands, request, out_type, creater,bound_list_id)
  167. log_success_operation(
  168. request=self.request,
  169. operation_content=f"出库分配成功,出库单ID:{bound_list_id},分配结果:{result}",
  170. operation_level="other",
  171. operator=self.request.auth.name if self.request.auth else None,
  172. object_id=bound_list_id,
  173. module_name="出库分配"
  174. )
  175. return self.build_success_response(result)
  176. except APIException as e:
  177. log_failure_operation(
  178. request=self.request,
  179. operation_content=f"出库分配失败,出库单ID:{request.data.get('bound_list_id')},错误:{e.detail}",
  180. operation_level="other",
  181. operator=self.request.auth.name if self.request.auth else None,
  182. module_name="出库分配"
  183. )
  184. return self.build_error_response(e.detail, 200)
  185. except Exception as e:
  186. log_failure_operation(
  187. request=self.request,
  188. operation_content=f"出库分配异常,出库单ID:{request.data.get('bound_list_id')},错误:{str(e)}",
  189. operation_level="other",
  190. operator=self.request.auth.name if self.request.auth else None,
  191. module_name="出库分配"
  192. )
  193. return self.build_error_response(str(e), 200)
  194. # 验证层方法
  195. def validate_distribute_request(self, request):
  196. """验证请求参数"""
  197. bound_list_id = request.data.get('bound_list_id')
  198. if not bound_list_id:
  199. raise APIException({"detail": "Missing bound_list_id"})
  200. demands = OutBoundDemandModel.objects.filter(
  201. bound_list_id=bound_list_id,
  202. is_delete=False
  203. )
  204. if not demands.exists():
  205. raise APIException({"detail": "No demands found"})
  206. base_info = OutBoundDemandModel.objects.filter(
  207. bound_list_id=bound_list_id,
  208. is_delete=False
  209. ).first()
  210. out_type = base_info.out_type
  211. creater = base_info.creater
  212. return bound_list_id, demands, out_type, creater
  213. # 数据处理层方法
  214. def aggregate_demands(self, demands):
  215. """合并相同物料需求"""
  216. return demands.values('goods_code').annotate(
  217. total_demand=Sum('goods_qty')
  218. )
  219. # 核心分配逻辑
  220. def process_all_goods(self, aggregated_demands, request, out_type, creater,bound_list_id):
  221. """处理所有物料分配"""
  222. return [
  223. self.process_single_goods(
  224. goods_code=item['goods_code'],
  225. total_demand=item['total_demand'],
  226. request=request,
  227. out_type=out_type,
  228. creater=creater,
  229. bound_list_id=bound_list_id
  230. )
  231. for item in aggregated_demands
  232. ]
  233. def process_single_goods(self, goods_code, total_demand, request,out_type, creater,bound_list_id):
  234. """处理单个物料分配"""
  235. batches = self.get_available_batches(goods_code)
  236. remaining, allocations = self.allocate_batches(total_demand, batches, request,out_type, creater,bound_list_id)
  237. if remaining > 0:
  238. raise APIException({
  239. "detail": f"Insufficient stock for {goods_code}",
  240. "required": total_demand,
  241. "allocated": total_demand - remaining
  242. })
  243. return {
  244. "goods_code": goods_code,
  245. "total_demand": total_demand,
  246. "allocations": allocations
  247. }
  248. def get_available_batches(self, goods_code):
  249. """获取可用入库批次"""
  250. return BoundBatchModel.objects.filter(
  251. goods_code=goods_code,
  252. is_delete=False
  253. ).order_by('bound_batch_order')
  254. # 批次分配逻辑
  255. def allocate_batches(self, total_demand, batches, request,out_type, creater,bound_list_id):
  256. """分配具体批次"""
  257. remaining = total_demand
  258. allocations = []
  259. for batch in batches:
  260. if remaining <= 0:
  261. break
  262. allocated = self.allocate_single_batch(
  263. batch=batch,
  264. remaining=remaining,
  265. request=request,
  266. out_type=out_type,
  267. creater=creater,
  268. bound_list_id=bound_list_id
  269. )
  270. if allocated == 0:
  271. continue
  272. allocations.append(allocated)
  273. remaining -= allocated['allocated']
  274. return remaining, allocations
  275. def allocate_single_batch(self, batch, remaining, request,out_type, creater,bound_list_id):
  276. """单个批次分配逻辑"""
  277. available = batch.goods_in_location_qty - batch.goods_out_qty
  278. if available <= 0:
  279. return 0
  280. allocate_qty = min(remaining, available)
  281. self.update_batch_status(batch, allocate_qty)
  282. out_batch = self.create_out_batch(batch, allocate_qty, request,out_type, creater,bound_list_id)
  283. return {
  284. "batch": batch.bound_number,
  285. "allocated": allocate_qty,
  286. "out_batch": out_batch.out_number
  287. }
  288. # 数据操作层方法
  289. def update_batch_status(self, batch, allocate_qty):
  290. """更新批次状态和数量"""
  291. batch.goods_out_qty += allocate_qty
  292. if batch.goods_out_qty == batch.goods_in_location_qty:
  293. batch.status = 6 # 已出库
  294. elif batch.goods_out_qty > 0:
  295. batch.status = 5 # 部分出库
  296. batch.save()
  297. def create_out_batch(self, batch, allocate_qty, request,out_type, creater,bound_list_id):
  298. """创建出库记录"""
  299. out_data = {
  300. 'bound_list': bound_list_id,
  301. 'out_number': self.generate_out_number(batch.goods_code),
  302. 'batch_number': batch.id,
  303. 'out_date': timezone.now(),
  304. 'warehouse_code': batch.warehouse_code,
  305. 'warehouse_name': batch.warehouse_name,
  306. 'goods_code': batch.goods_code,
  307. 'goods_desc': batch.goods_desc,
  308. 'goods_out_qty': allocate_qty,
  309. 'status': 0,
  310. 'openid': request.auth.openid,
  311. 'out_type': out_type,
  312. 'creater': creater,
  313. }
  314. serializer = OutBatchPostSerializer(data=out_data)
  315. if not serializer.is_valid():
  316. raise APIException({
  317. "detail": f"Serialization error for {batch.goods_code}",
  318. "errors": serializer.errors
  319. })
  320. return serializer.save()
  321. # 工具方法
  322. def generate_out_number(self, goods_code):
  323. """生成唯一出库单号"""
  324. timestamp = timezone.now().strftime("%Y%m%d%H%M%S")
  325. import uuid
  326. return f"OUT-{goods_code}-{timestamp}-{uuid.uuid4().hex[:6]}"
  327. def build_success_response(self, data):
  328. """构建成功响应"""
  329. return Response({
  330. "code": 200,
  331. "msg": "Distribution completed",
  332. "data": data
  333. })
  334. def build_error_response(self, error, status_code):
  335. """构建错误响应"""
  336. return Response({
  337. "code": status_code,
  338. "msg": "Distribution failed" if status_code == 400 else "Server error",
  339. "error": error
  340. }, status=status_code)
  341. # 物料统计视图类
  342. class MaterialStatisticsViewSet(viewsets.ModelViewSet):
  343. """
  344. retrieve:
  345. Response a data list(get)
  346. list:
  347. Response a data list(all)
  348. create:
  349. Create a data line(post)
  350. delete:
  351. Delete a data line(delete)
  352. """
  353. # authentication_classes = [] # 禁用所有认证类
  354. # permission_classes = [AllowAny] # 允许任意访问
  355. pagination_class = MyPageNumberPagination
  356. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  357. ordering_fields = ['id', "create_time", "update_time", ]
  358. filter_class = MaterialStatisticsFilter
  359. def get_project(self):
  360. try:
  361. id = self.kwargs.get('pk')
  362. return id
  363. except:
  364. return None
  365. def get_queryset(self):
  366. id = self.get_project()
  367. if self.request.user:
  368. if id is None:
  369. return MaterialStatistics.objects.filter()
  370. else:
  371. return MaterialStatistics.objects.filter(id=id)
  372. else:
  373. return MaterialStatistics.objects.none()
  374. def get_serializer_class(self):
  375. if self.action in ['list' ]:
  376. return MaterialStatisticsSerializer
  377. elif self.action in ['retrieve']:
  378. return MaterialStatisticsSerializer_items
  379. else:
  380. return self.http_method_not_allowed(request=self.request)
  381. # 汇报单类视图
  382. class BoundListViewSet(viewsets.ModelViewSet):
  383. """
  384. retrieve:
  385. Response a data list(get)
  386. list:
  387. Response a data list(all)
  388. create:
  389. Create a data line(post)
  390. delete:
  391. Delete a data line(delete)
  392. """
  393. # authentication_classes = [] # 禁用所有认证类
  394. # permission_classes = [AllowAny] # 允许任意访问
  395. pagination_class = MyPageNumberPagination
  396. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  397. ordering_fields = ['id', "create_time", "update_time", ]
  398. filter_class = BoundListFilter
  399. def get_project(self):
  400. try:
  401. id = self.kwargs.get('pk')
  402. return id
  403. except:
  404. return None
  405. def get_queryset(self):
  406. id = self.get_project()
  407. if self.request.user:
  408. if id is None:
  409. return BoundListModel.objects.filter( is_delete=False)
  410. else:
  411. log_operation(
  412. request=self.request,
  413. operation_content=f"查看入库单详情 ID:{id}",
  414. operation_level="view",
  415. operator=self.request.auth.name if self.request.auth else None,
  416. object_id=id,
  417. module_name="入库单"
  418. )
  419. return BoundListModel.objects.filter( id=id, is_delete=False)
  420. else:
  421. return BoundListModel.objects.none()
  422. def get_serializer_class(self):
  423. if self.action in ['list', 'destroy','retrieve']:
  424. return BoundListGetSerializer
  425. elif self.action in ['create', 'update']:
  426. return BoundListPostSerializer
  427. else:
  428. return self.http_method_not_allowed(request=self.request)
  429. def create(self, request, *args, **kwargs):
  430. data = self.request.data
  431. # if BoundListModel.objects.filter(code=data['code'], is_delete=False).exists():
  432. # raise APIException({"detail": "Data exists"})
  433. # else:
  434. data['openid'] = self.request.auth.openid
  435. data['bound_date'] =str(timezone.now().strftime('%Y-%m-%d'))
  436. order_day=str(timezone.now().strftime('%Y-%m-'))
  437. data['bound_month'] =str(timezone.now().strftime('%Y%m'))
  438. if data['bound_type'] == 'in':
  439. data['bound_status'] = '100'
  440. else:
  441. data['bound_status'] = '200'
  442. qs_set = BoundListModel.objects.filter(bound_month=data['bound_month'], bound_code_type=data['bound_code_type'], is_delete=False)
  443. print('qs_set是:', len(qs_set))
  444. if len(qs_set) > 0:
  445. bound_last_code = qs_set.order_by('-id').first().bound_code
  446. data['bound_code'] = data['bound_code_type'] +'-'+ order_day + str(int(bound_last_code.split('-')[-1])+1).zfill(4)
  447. else:
  448. data['bound_code'] = data['bound_code_type'] +'-'+ order_day + '0001'
  449. serializer = self.get_serializer(data=data)
  450. serializer.is_valid(raise_exception=True)
  451. try:
  452. serializer.save()
  453. headers = self.get_success_headers(serializer.data)
  454. log_success_operation(
  455. request=self.request,
  456. operation_content=f"创建入库单 ID:{serializer.data['id']},创建内容:{data}",
  457. operation_level="new",
  458. operator=self.request.auth.name if self.request.auth else None,
  459. object_id=serializer.data['id'],
  460. module_name="入库单"
  461. )
  462. return Response(serializer.data, status=200, headers=headers)
  463. except Exception as e:
  464. log_failure_operation(
  465. request=self.request,
  466. operation_content=f"创建入库单失败: {str(e)}, 创建内容:{data}",
  467. operation_level="new",
  468. operator=self.request.auth.name if self.request.auth else None,
  469. module_name="入库单"
  470. )
  471. raise
  472. def update(self, request, pk):
  473. qs = self.get_object()
  474. data = self.request.data
  475. try:
  476. serializer = self.get_serializer(qs, data=data)
  477. serializer.is_valid(raise_exception=True)
  478. serializer.save()
  479. headers = self.get_success_headers(serializer.data)
  480. log_success_operation(
  481. request=self.request,
  482. operation_content=f"更新入库单 ID:{serializer.data['id']}:{data}",
  483. operation_level="update",
  484. operator=self.request.auth.name if self.request.auth else None,
  485. object_id=serializer.data['id'],
  486. module_name="入库单"
  487. )
  488. return Response(serializer.data, status=200, headers=headers)
  489. except Exception as e:
  490. log_failure_operation(
  491. request=self.request,
  492. operation_content=f"更新入库单失败 ID:{qs.id}:{str(e)}, 更新内容:{data}",
  493. operation_level="update",
  494. operator=self.request.auth.name if self.request.auth else None,
  495. object_id=qs.id,
  496. module_name="入库单"
  497. )
  498. raise
  499. def destroy(self, request, pk):
  500. qs = self.get_object()
  501. # if qs.openid != self.request.auth.openid:
  502. # raise APIException({"detail": "该入库非您所属,禁止删除,您可以进行编辑"})
  503. # else:
  504. qs.is_delete = True
  505. qs.bound_code =qs.bound_code+'-delete'+str(timezone.now().strftime('%Y%m%d%H%M%S'))
  506. qs.save()
  507. if qs.bound_type == 'in':
  508. BoundDetailModel.objects.filter(bound_list=qs.id).update(is_delete=True)
  509. if qs.relate_bill:
  510. qs.relate_bill.bound_status = 0
  511. qs.relate_bill.save()
  512. else:
  513. OutBoundDemandModel.objects.filter(bound_list=qs.id).update(is_delete=True)
  514. if qs.relate_out_bill:
  515. qs.relate_out_bill.bound_status = 0
  516. qs.relate_out_bill.save()
  517. serializer = self.get_serializer(qs, many=False)
  518. headers = self.get_success_headers(serializer.data)
  519. log_success_operation(
  520. request=self.request,
  521. operation_content=f"删除入库单 ID:{qs.id}",
  522. operation_level="delete",
  523. operator=self.request.auth.name if self.request.auth else None,
  524. object_id=qs.id,
  525. module_name="入库单"
  526. )
  527. return Response(serializer.data, status=200, headers=headers)
  528. # 入库批次类视图
  529. class BoundBatchViewSet(viewsets.ModelViewSet):
  530. """
  531. retrieve:
  532. Response a data list(get)
  533. list:
  534. Response a data list(all)
  535. create:
  536. Create a data line(post)
  537. delete:
  538. Delete a data line(delete)
  539. """
  540. # authentication_classes = [] # 禁用所有认证类
  541. # permission_classes = [AllowAny] # 允许任意访问
  542. pagination_class = MyPageNumberPagination
  543. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  544. ordering_fields = ['id', "create_time", "update_time", ]
  545. filter_class = BoundBatchFilter
  546. def get_project(self):
  547. try:
  548. id = self.kwargs.get('pk')
  549. return id
  550. except:
  551. return None
  552. def get_queryset(self):
  553. id = self.get_project()
  554. if self.request.user:
  555. if id is None:
  556. return BoundBatchModel.objects.filter( is_delete=False)
  557. else:
  558. return BoundBatchModel.objects.filter( id=id, is_delete=False)
  559. else:
  560. return BoundBatchModel.objects.none()
  561. def get_serializer_class(self):
  562. if self.action in ['list', 'destroy','retrieve']:
  563. return BoundBatchGetSerializer
  564. elif self.action in ['create', 'update']:
  565. return BoundBatchPostSerializer
  566. else:
  567. return self.http_method_not_allowed(request=self.request)
  568. def create(self, request, *args, **kwargs):
  569. data = self.request.data
  570. try:
  571. data['openid'] = self.request.auth.openid
  572. data['goods_total_weight'] = data['goods_weight']*data['goods_qty']
  573. order_day=str(timezone.now().strftime('-%Y%m'))
  574. order_month=str(timezone.now().strftime('%Y%m'))
  575. data['bound_month'] =str(timezone.now().strftime('%Y%m'))
  576. # print(data['order'])
  577. if data['order'] == 'true':
  578. qs_set = BoundBatchModel.objects.filter( goods_code=data['goods_code'], bound_month=order_month, is_delete=False)
  579. print('qs_set是:', len(qs_set))
  580. if len(qs_set) > 0:
  581. bound_last_code = qs_set.order_by('-bound_batch_order').first().bound_number
  582. data['bound_batch_order'] = int(bound_last_code.split('-')[-1])+1
  583. data['bound_number'] = data['goods_code'] + '-' + str(int(bound_last_code.split('-')[-1])+1)
  584. else:
  585. data['bound_batch_order'] = int(order_day.split('-')[-1])*1000 +1
  586. data['bound_number'] = data['goods_code'] + order_day + '001'
  587. else:
  588. data['bound_number'] = data['goods_code'] + '-' + str(data['bound_batch_order'])
  589. batch_obj = BoundBatchModel.objects.filter(bound_number=data['bound_number'], is_delete=False)
  590. if batch_obj.exists():
  591. # 批次号已存在,追加数据到原批次
  592. existing_batch = batch_obj.first()
  593. existing_batch.goods_qty += data['goods_qty']
  594. existing_batch.goods_total_weight += data['goods_total_weight']
  595. existing_batch.update_time = timezone.now()
  596. existing_batch.save()
  597. log_success_operation(
  598. request=self.request,
  599. operation_content=f"创建入库批次 ID:{existing_batch.id} 成功,追加数据:{data}",
  600. operation_level="create",
  601. operator=self.request.auth.name if self.request.auth else None,
  602. object_id=existing_batch.id,
  603. module_name="入库批次"
  604. )
  605. self.add_batch_log(existing_batch.__dict__, 0, data['goods_qty'])
  606. serializer = self.get_serializer(existing_batch, many=False)
  607. headers = self.get_success_headers(serializer.data)
  608. return Response(serializer.data, status=200, headers=headers)
  609. else:
  610. serializer = self.get_serializer(data=data)
  611. serializer.is_valid(raise_exception=True)
  612. serializer.save()
  613. headers = self.get_success_headers(serializer.data)
  614. self.add_batch_log(serializer.data, 0, data['goods_qty'])
  615. log_success_operation(
  616. request=self.request,
  617. operation_content=f"创建入库批次 ID:{serializer.data['id']},创建内容:{data}",
  618. operation_level="create",
  619. operator=self.request.auth.name if self.request.auth else None,
  620. object_id=serializer.data['id'],
  621. module_name="入库批次"
  622. )
  623. return Response(serializer.data, status=200, headers=headers)
  624. except Exception as e:
  625. print(e)
  626. log_failure_operation(
  627. request=self.request,
  628. operation_content=f"创建入库批次失败: {str(e)}, 创建内容:{data}",
  629. operation_level="new",
  630. operator=self.request.auth.name if self.request.auth else None,
  631. module_name="入库批次"
  632. )
  633. raise APIException({"detail": "{}".format(e)})
  634. def add_batch_log(self, data, log_type, goods_qty):
  635. choices_dict = dict(BatchOperateLogModel.BATCH_LOG_TYPE)
  636. log_type_name = choices_dict.get(log_type, "未知类型")
  637. try:
  638. # 获取 BoundBatchModel 实例
  639. batch_obj = BoundBatchModel.objects.get(id=data['id'])
  640. except BoundBatchModel.DoesNotExist:
  641. return False
  642. log_data = {
  643. 'batch_id': batch_obj,
  644. 'log_type': log_type,
  645. 'log_date': timezone.now().strftime('%Y-%m-%d-%H:%M'), # 直接格式化时间,无需转字符串
  646. 'goods_code': data['goods_code'],
  647. 'goods_desc': data['goods_desc'],
  648. 'goods_qty': data['goods_qty'],
  649. 'log_content': f"{log_type_name} {data['goods_qty']}件",
  650. 'creater': data['creater'],
  651. 'openid': data['openid'],
  652. 'is_delete': False,
  653. }
  654. # 创建日志记录
  655. BatchOperateLogModel.objects.create(**log_data)
  656. return True
  657. def update(self, request, pk):
  658. qs = self.get_object()
  659. data = self.request.data
  660. try:
  661. serializer = self.get_serializer(qs, data=data)
  662. serializer.is_valid(raise_exception=True)
  663. serializer.save()
  664. headers = self.get_success_headers(serializer.data)
  665. log_success_operation(
  666. request=self.request,
  667. operation_content=f"更新入库批次 ID:{serializer.data['id']}:{data}",
  668. operation_level="update",
  669. operator=self.request.auth.name if self.request.auth else None,
  670. object_id=serializer.data['id'],
  671. module_name="入库批次"
  672. )
  673. return Response(serializer.data, status=200, headers=headers)
  674. except Exception as e:
  675. log_failure_operation(
  676. request=self.request,
  677. operation_content=f"更新入库批次失败 ID:{qs.id}:{str(e)}, 更新内容:{data}",
  678. operation_level="update",
  679. operator=self.request.auth.name if self.request.auth else None,
  680. object_id=qs.id,
  681. module_name="入库批次"
  682. )
  683. raise
  684. def destroy(self, request, pk):
  685. qs = self.get_object()
  686. if qs.openid != self.request.auth.openid:
  687. log_failure_operation(
  688. request=self.request,
  689. operation_content=f"删除入库批次 ID:{qs.id} 失败,非所属用户操作",
  690. operation_level="delete",
  691. operator=self.request.auth.name if self.request.auth else None,
  692. object_id=qs.id,
  693. module_name="入库批次"
  694. )
  695. raise APIException({"detail": "该入库非您所属,禁止删除,您可以进行编辑"})
  696. else:
  697. qs.is_delete = True
  698. qs.save()
  699. serializer = self.get_serializer(qs, many=False)
  700. headers = self.get_success_headers(serializer.data)
  701. log_success_operation(
  702. request=self.request,
  703. operation_content=f"删除入库批次 ID:{qs.id}",
  704. operation_level="delete",
  705. operator=self.request.auth.name if self.request.auth else None,
  706. object_id=qs.id,
  707. module_name="入库批次"
  708. )
  709. return Response(serializer.data, status=200, headers=headers)
  710. def batchinout(self, request, *args, **kwargs):
  711. from container.models import ContainerDetailModel, ContainerListModel
  712. from decimal import Decimal
  713. batch_number = request.data.get('batch_number')
  714. batch_in_qty = request.data.get('batch_in_qty')
  715. if not batch_number:
  716. log_failure_operation(
  717. request=self.request,
  718. operation_content=f"批次进出库失败,批次号不能为空",
  719. operation_level="other",
  720. operator=self.request.auth.name if self.request.auth else None,
  721. module_name="入库批次"
  722. )
  723. return Response({"code": 200,"message": "批次号不能为空","data": "批次进出库数目:{batch_in_qty}"}, status=200)
  724. try:
  725. batch_obj = BoundBatchModel.objects.get(bound_number=batch_number, is_delete=False)
  726. except BoundBatchModel.DoesNotExist:
  727. log_failure_operation(
  728. request=self.request,
  729. operation_content=f"批次进出库失败,批次号不存在:{batch_number}",
  730. operation_level="other",
  731. operator=self.request.auth.name if self.request.auth else None,
  732. module_name="入库批次"
  733. )
  734. return Response({"code": 200,"message": "批次号不存在","data": "批次进出库数目:{batch_in_qty}"}, status=200)
  735. if batch_in_qty:
  736. # 获取或创建虚拟容器
  737. virtual_container, created = ContainerListModel.objects.get_or_create(
  738. container_code=1
  739. )
  740. # 使用 update_or_create 避免并发问题
  741. virtual_container_detail = ContainerDetailModel.objects.create(
  742. month=timezone.now().strftime('%Y%m'),
  743. container= virtual_container,
  744. batch= batch_obj,
  745. goods_class= 1, # 成品
  746. goods_code= batch_obj.goods_code,
  747. goods_desc= batch_obj.goods_desc,
  748. goods_qty= Decimal(batch_in_qty),
  749. goods_out_qty= Decimal(0),
  750. goods_weight= batch_obj.goods_weight,
  751. status= 2, # 在库
  752. creater= "system",
  753. create_time= timezone.now(),
  754. update_time= timezone.now(),
  755. is_delete= False )
  756. virtual_container_detail.save()
  757. virtual_container_detail.goods_out_qty = batch_in_qty
  758. virtual_container_detail.save()
  759. log_success_operation(
  760. request=self.request,
  761. operation_content=f"批次进出库 ID:{batch_obj.id} 成功,进出库数目:{batch_in_qty}",
  762. operation_level="create",
  763. operator=self.request.auth.name if self.request.auth else None,
  764. object_id=batch_obj.id,
  765. module_name="入库批次"
  766. )
  767. return Response({"code": 200,"message": "操作成功","data": "批次进出库数目:{batch_in_qty}"}, status=200)
  768. class BatchFileDownloadView(viewsets.ModelViewSet):
  769. """批次文件下载视图类"""
  770. renderer_classes = (BatchFileRenderCN, ) + tuple(api_settings.DEFAULT_RENDERER_CLASSES)
  771. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  772. ordering_fields = ['id', "create_time", "update_time", ]
  773. filter_class = BoundBatchFilter
  774. def get_project(self):
  775. try:
  776. id = self.kwargs.get('pk')
  777. return id
  778. except:
  779. return None
  780. def get_queryset(self):
  781. id = self.get_project()
  782. if self.request.user:
  783. if id is None:
  784. queryset = BoundBatchModel.objects.filter(is_delete=False)
  785. # 支持 goods_in_location_qty__gt 过滤器,用于只下载在库数量大于0的批次
  786. if self.request.query_params.get('goods_in_location_qty__gt'):
  787. queryset = queryset.filter(goods_in_location_qty__gt=0)
  788. return queryset
  789. else:
  790. return BoundBatchModel.objects.filter(id=id, is_delete=False)
  791. else:
  792. return BoundBatchModel.objects.none()
  793. def get_serializer_class(self):
  794. if self.action in ['list']:
  795. return BoundBatchGetSerializer
  796. else:
  797. return self.http_method_not_allowed(request=self.request)
  798. def get_render(self, data):
  799. return BatchFileRenderCN().render(data)
  800. def list(self, request, *args, **kwargs):
  801. from datetime import datetime
  802. dt = datetime.now()
  803. data = (
  804. BoundBatchGetSerializer(instance).data
  805. for instance in self.filter_queryset(self.get_queryset())
  806. )
  807. renderer = self.get_render(data)
  808. response = StreamingHttpResponse(
  809. renderer,
  810. content_type="text/csv"
  811. )
  812. response['Content-Disposition'] = "attachment; filename='batch_剩余量_{}.csv'".format(str(dt.strftime('%Y%m%d%H%M%S%f')))
  813. log_operation(
  814. request=self.request,
  815. operation_content=f"下载批次剩余量文件",
  816. operation_level="download",
  817. operator=self.request.auth.name if self.request.auth else None,
  818. module_name="批次文件下载"
  819. )
  820. return response
  821. # 入库明细类视图
  822. class BoundDetailViewSet(viewsets.ModelViewSet):
  823. """
  824. retrieve:
  825. Response a data list(get)
  826. list:
  827. Response a data list(all)
  828. create:
  829. Create a data line(post)
  830. delete:
  831. Delete a data line(delete)
  832. """
  833. # authentication_classes = [] # 禁用所有认证类
  834. # permission_classes = [AllowAny] # 允许任意访问
  835. pagination_class = MyPageNumberPagination
  836. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  837. ordering_fields = ['id', "create_time", "update_time", ]
  838. filter_class = BoundDetailFilter
  839. def get_project(self):
  840. try:
  841. id = self.kwargs.get('pk')
  842. return id
  843. except:
  844. return None
  845. def get_queryset(self):
  846. id = self.get_project()
  847. if self.request.user:
  848. if id is None:
  849. return BoundDetailModel.objects.filter( is_delete=False)
  850. else:
  851. return BoundDetailModel.objects.filter( id=id, is_delete=False)
  852. else:
  853. return BoundDetailModel.objects.none()
  854. def get_serializer_class(self):
  855. if self.action in ['list', 'destroy','retrieve']:
  856. return BoundDetailGetSerializer
  857. elif self.action in ['create', 'update']:
  858. return BoundDetailPostSerializer
  859. else:
  860. return self.http_method_not_allowed(request=self.request)
  861. def create(self, request, *args, **kwargs):
  862. data = self.request.data
  863. data['openid'] = self.request.auth.openid
  864. data.setdefault('is_delete', False)
  865. # 验证并保存数据
  866. data['detail_code'] = f"DC-{data['bound_list']:02}{data['bound_batch']:02}"
  867. print(data['detail_code'])
  868. if BoundDetailModel.objects.filter(bound_list=data['bound_list'], bound_batch=data['bound_batch'], is_delete=False).exists():
  869. detail_obj = BoundDetailModel.objects.get(bound_list=data['bound_list'], bound_batch=data['bound_batch'], is_delete=False)
  870. serializer = self.get_serializer(detail_obj, many=False)
  871. headers = self.get_success_headers(serializer.data)
  872. log_operation(
  873. request=self.request,
  874. operation_content=f"查询入库明细,入库单ID:{data['bound_list']}, 批次ID:{data['bound_batch']}",
  875. operation_level="view",
  876. operator=self.request.auth.name if self.request.auth else None,
  877. object_id=detail_obj.id,
  878. module_name="入库明细"
  879. )
  880. return Response(serializer.data, status=200, headers=headers)
  881. else:
  882. serializer = self.get_serializer(data=data)
  883. serializer.is_valid(raise_exception=True)
  884. serializer.save()
  885. # 返回响应
  886. headers = self.get_success_headers(serializer.data)
  887. log_success_operation(
  888. request=self.request,
  889. operation_content=f"创建入库明细 ID:{serializer.data['id']},创建内容:{data}",
  890. operation_level="create",
  891. operator=self.request.auth.name if self.request.auth else None,
  892. object_id=serializer.data['id'],
  893. module_name="入库明细"
  894. )
  895. return Response(serializer.data, status=200, headers=headers)
  896. def update(self, request, pk):
  897. qs = self.get_object()
  898. data = self.request.data
  899. try:
  900. serializer = self.get_serializer(qs, data=data)
  901. serializer.is_valid(raise_exception=True)
  902. serializer.save()
  903. headers = self.get_success_headers(serializer.data)
  904. log_success_operation(
  905. request=self.request,
  906. operation_content=f"更新入库明细 ID:{serializer.data['id']}:{data}",
  907. operation_level="update",
  908. operator=self.request.auth.name if self.request.auth else None,
  909. object_id=serializer.data['id'],
  910. module_name="入库明细"
  911. )
  912. return Response(serializer.data, status=200, headers=headers)
  913. except Exception as e:
  914. log_failure_operation(
  915. request=self.request,
  916. operation_content=f"更新入库明细失败 ID:{qs.id}:{str(e)}, 更新内容:{data}",
  917. operation_level="update",
  918. operator=self.request.auth.name if self.request.auth else None,
  919. object_id=qs.id,
  920. module_name="入库明细"
  921. )
  922. raise
  923. def destroy(self, request, pk):
  924. qs = self.get_object()
  925. if qs.openid != self.request.auth.openid:
  926. raise APIException({"detail": "该入库非您所属,禁止删除,您可以进行编辑"})
  927. log_failure_operation(
  928. request=self.request,
  929. operation_content=f"删除入库明细 ID:{qs.id} 失败,非所属用户操作",
  930. operation_level="delete",
  931. operator=self.request.auth.name if self.request.auth else None,
  932. object_id=qs.id,
  933. module_name="入库明细"
  934. )
  935. else:
  936. qs.is_delete = True
  937. qs.save()
  938. serializer = self.get_serializer(qs, many=False)
  939. headers = self.get_success_headers(serializer.data)
  940. log_success_operation(
  941. request=self.request,
  942. operation_content=f"删除入库明细 ID:{qs.id}",
  943. operation_level="delete",
  944. operator=self.request.auth.name if self.request.auth else None,
  945. object_id=qs.id,
  946. module_name="入库明细"
  947. )
  948. return Response(serializer.data, status=200, headers=headers)
  949. # 出库明细类视图
  950. class OutBoundDetailViewSet(viewsets.ModelViewSet):
  951. """
  952. retrieve:
  953. Response a data list(get)
  954. list: Response a data list(all)
  955. create:
  956. Create a data line(post)
  957. delete:
  958. Delete a data line(delete)
  959. """
  960. # authentication_classes = [] # 禁用所有认证类
  961. # permission_classes = [AllowAny] # 允许任意访问
  962. pagination_class = MyPageNumberPagination
  963. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  964. ordering_fields = ['id', "create_time", "update_time", ]
  965. filter_class = OutBoundDetailFilter
  966. def get_project(self):
  967. try:
  968. id = self.kwargs.get('pk')
  969. return id
  970. except:
  971. return None
  972. def get_queryset(self):
  973. id = self.get_project()
  974. if self.request.user:
  975. if id is None:
  976. return OutBoundDetailModel.objects.filter( is_delete=False)
  977. else:
  978. return OutBoundDetailModel.objects.filter( id=id, is_delete=False)
  979. else:
  980. return OutBoundDetailModel.objects.none()
  981. def get_serializer_class(self):
  982. if self.action in ['list', 'destroy','retrieve']:
  983. return OutBoundDetailGetSerializer
  984. elif self.action in ['create', 'update']:
  985. return OutBoundDetailPostSerializer
  986. else:
  987. return self.http_method_not_allowed(request=self.request)
  988. def create(self, request, *args, **kwargs):
  989. data = self.request.data
  990. try:
  991. data['openid'] = self.request.auth.openid
  992. data.setdefault('is_delete', False)
  993. data['bound_batch_number'] = OutBatchModel.objects.get(id=data['bound_batch']).batch_number.id
  994. # 验证并保存数据
  995. data['detail_code'] = f"DC-{data['bound_list']:02}{data['bound_batch']:02}"
  996. # print(data['detail_code'])
  997. if OutBoundDetailModel.objects.filter(detail_code=data['detail_code'], is_delete=False).exists():
  998. # 这里追加数目
  999. detail_obj = OutBoundDetailModel.objects.filter(detail_code=data['detail_code'], is_delete=False).first()
  1000. OutBoundDetailModel.objects.filter(detail_code=data['detail_code'], is_delete=False).update(goods_qty=F('goods_qty')+data['goods_qty'])
  1001. log_success_operation(
  1002. request=self.request,
  1003. operation_content=f"更新出库明细 ID:{detail_obj.id},数量追加:{data['goods_qty']}",
  1004. operation_level="update",
  1005. operator=self.request.auth.name if self.request.auth else None,
  1006. object_id=detail_obj.id,
  1007. module_name="出库明细"
  1008. )
  1009. return Response({"detail": "数据存在,数量追加"}, status=200)
  1010. # raise APIException({"detail": "Data exists"})
  1011. else:
  1012. serializer = self.get_serializer(data=data)
  1013. serializer.is_valid(raise_exception=True)
  1014. serializer.save()
  1015. # 返回响应
  1016. headers = self.get_success_headers(serializer.data)
  1017. log_success_operation(
  1018. request=self.request,
  1019. operation_content=f"创建出库明细 ID:{serializer.data['id']},创建内容:{data}",
  1020. operation_level="create",
  1021. operator=self.request.auth.name if self.request.auth else None,
  1022. object_id=serializer.data['id'],
  1023. module_name="出库明细"
  1024. )
  1025. return Response(serializer.data, status=200, headers=headers)
  1026. except Exception as e:
  1027. log_failure_operation(
  1028. request=self.request,
  1029. operation_content=f"创建出库明细失败: {str(e)}, 创建内容:{data}",
  1030. operation_level="new",
  1031. operator=self.request.auth.name if self.request.auth else None,
  1032. module_name="出库明细"
  1033. )
  1034. raise
  1035. def update(self, request, pk):
  1036. qs = self.get_object()
  1037. data = self.request.data
  1038. try:
  1039. serializer = self.get_serializer(qs, data=data)
  1040. serializer.is_valid(raise_exception=True)
  1041. serializer.save()
  1042. headers = self.get_success_headers(serializer.data)
  1043. log_success_operation(
  1044. request=self.request,
  1045. operation_content=f"更新出库明细 ID:{serializer.data['id']}:{data}",
  1046. operation_level="update",
  1047. operator=self.request.auth.name if self.request.auth else None,
  1048. object_id=serializer.data['id'],
  1049. module_name="出库明细"
  1050. )
  1051. return Response(serializer.data, status=200, headers=headers)
  1052. except Exception as e:
  1053. log_failure_operation(
  1054. request=self.request,
  1055. operation_content=f"更新出库明细失败 ID:{qs.id}:{str(e)}, 更新内容:{data}",
  1056. operation_level="update",
  1057. operator=self.request.auth.name if self.request.auth else None,
  1058. object_id=qs.id,
  1059. module_name="出库明细"
  1060. )
  1061. raise
  1062. def destroy(self, request, pk):
  1063. qs = self.get_object()
  1064. if qs.openid != self.request.auth.openid:
  1065. raise APIException({"detail": "该入库非您所属,禁止删除,您可以进行编辑"})
  1066. log_failure_operation(
  1067. request=self.request,
  1068. operation_content=f"删除出库明细 ID:{qs.id} 失败,非所属用户操作",
  1069. operation_level="delete",
  1070. operator=self.request.auth.name if self.request.auth else None,
  1071. object_id=qs.id,
  1072. module_name="出库明细"
  1073. )
  1074. else:
  1075. qs.is_delete = True
  1076. qs.save()
  1077. serializer = self.get_serializer(qs, many=False)
  1078. headers = self.get_success_headers(serializer.data)
  1079. log_success_operation(
  1080. request=self.request,
  1081. operation_content=f"删除出库明细 ID:{qs.id}",
  1082. operation_level="delete",
  1083. operator=self.request.auth.name if self.request.auth else None,
  1084. object_id=qs.id,
  1085. module_name="出库明细"
  1086. )
  1087. return Response(serializer.data, status=200, headers=headers)
  1088. # 出库批次类视图
  1089. class OutBoundBatchViewSet(viewsets.ModelViewSet):
  1090. """
  1091. retrieve:
  1092. Response a data list(get)
  1093. list:
  1094. Response a data list(all)
  1095. create:
  1096. Create a data line(post)
  1097. delete:
  1098. Delete a data line(delete)
  1099. """
  1100. # authentication_classes = [] # 禁用所有认证类
  1101. # permission_classes = [AllowAny] # 允许任意访问
  1102. pagination_class = MyPageNumberPagination
  1103. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  1104. ordering_fields = ['id', "create_time", "update_time", ]
  1105. filter_class = OutBatchFilter
  1106. def get_project(self):
  1107. try:
  1108. id = self.kwargs.get('pk')
  1109. return id
  1110. except:
  1111. return None
  1112. def get_queryset(self):
  1113. id = self.get_project()
  1114. if self.request.user:
  1115. if id is None:
  1116. return OutBatchModel.objects.filter( is_delete=False)
  1117. else:
  1118. return OutBatchModel.objects.filter( id=id, is_delete=False)
  1119. else:
  1120. return OutBatchModel.objects.none()
  1121. def get_serializer_class(self):
  1122. if self.action in ['list', 'destroy','retrieve']:
  1123. return OutBatchGetSerializer
  1124. elif self.action in ['create', 'update']:
  1125. return OutBatchPostSerializer
  1126. else:
  1127. return self.http_method_not_allowed(request=self.request)
  1128. def create(self, request, *args, **kwargs):
  1129. data = self.request.data
  1130. try:
  1131. batch_obj = BoundBatchModel.objects.get(bound_number=data['out_number'])
  1132. if batch_obj is None:
  1133. raise APIException({"detail": "批次不存在"})
  1134. bound_obj = BoundListModel.objects.get(id=data['bound_list_id'])
  1135. data['bound_list'] = bound_obj.id
  1136. data['batch_number'] = batch_obj.id
  1137. data['out_date'] = str(timezone.now().strftime('%Y-%m-%d %H:%M:%S'))
  1138. data['openid'] = self.request.auth.openid
  1139. data.setdefault('is_delete', False)
  1140. data['goods_total_weight'] = data['goods_weight']*data['goods_out_qty']
  1141. from decimal import Decimal
  1142. # data['goods_out_qty'] 是一个字符串或浮点数
  1143. data['goods_out_qty'] = Decimal(str(data['goods_out_qty']))
  1144. from decimal import Decimal
  1145. data['goods_out_qty'] = Decimal(str(data['goods_out_qty']))
  1146. data['goods_qty'] = batch_obj.goods_qty - batch_obj.goods_reserve_qty - data['goods_out_qty']
  1147. data['status'] = 0 #现在处于出库申请状态
  1148. serializer = self.get_serializer(data=data)
  1149. serializer.is_valid(raise_exception=True)
  1150. serializer.save()
  1151. headers = self.get_success_headers(serializer.data)
  1152. self.add_batch_log(serializer.data, 1, data['goods_out_qty'])
  1153. log_success_operation(
  1154. request=self.request,
  1155. operation_content=f"创建出库批次 ID:{serializer.data['id']},创建内容:{data}",
  1156. operation_level="create",
  1157. operator=self.request.auth.name if self.request.auth else None,
  1158. object_id=serializer.data['id'],
  1159. module_name="出库批次"
  1160. )
  1161. return Response(serializer.data, status=200, headers=headers)
  1162. except Exception as e:
  1163. log_failure_operation(
  1164. request=self.request,
  1165. operation_content=f"创建出库批次失败: {str(e)}, 创建内容:{data}",
  1166. operation_level="new",
  1167. operator=self.request.auth.name if self.request.auth else None,
  1168. module_name="出库批次"
  1169. )
  1170. raise
  1171. def update(self, request, pk):
  1172. qs = self.get_object()
  1173. data = self.request.data
  1174. try:
  1175. data['openid'] = self.request.auth.openid
  1176. data.setdefault('is_delete', False)
  1177. data['goods_total_weight'] = data['goods_weight']*data['goods_qty']
  1178. serializer = self.get_serializer(qs, data=data)
  1179. serializer.is_valid(raise_exception=True)
  1180. serializer.save()
  1181. headers = self.get_success_headers(serializer.data)
  1182. self.add_batch_log(serializer.data, 1, data['goods_qty'])
  1183. log_success_operation(
  1184. request=self.request,
  1185. operation_content=f"更新出库批次 ID:{serializer.data['id']}:{data}",
  1186. operation_level="update",
  1187. operator=self.request.auth.name if self.request.auth else None,
  1188. object_id=serializer.data['id'],
  1189. module_name="出库批次"
  1190. )
  1191. return Response(serializer.data, status=200, headers=headers)
  1192. except Exception as e:
  1193. log_failure_operation(
  1194. request=self.request,
  1195. operation_content=f"更新出库批次失败 ID:{qs.id}:{str(e)}, 更新内容:{data}",
  1196. operation_level="update",
  1197. operator=self.request.auth.name if self.request.auth else None,
  1198. object_id=qs.id,
  1199. module_name="出库批次"
  1200. )
  1201. raise
  1202. def destroy(self, request, pk):
  1203. qs = self.get_object()
  1204. if qs.openid != self.request.auth.openid:
  1205. raise APIException({"detail": "该出库非您所属,禁止删除,您可以进行编辑"})
  1206. log_failure_operation(
  1207. request=self.request,
  1208. operation_content=f"删除出库批次 ID:{qs.id} 失败,非所属用户操作",
  1209. operation_level="delete",
  1210. operator=self.request.auth.name if self.request.auth else None,
  1211. object_id=qs.id,
  1212. module_name="出库批次"
  1213. )
  1214. else:
  1215. qs.is_delete = True
  1216. qs.save()
  1217. serializer = self.get_serializer(qs, many=False)
  1218. headers = self.get_success_headers(serializer.data)
  1219. log_success_operation(
  1220. request=self.request,
  1221. operation_content=f"删除出库批次 ID:{qs.id}",
  1222. operation_level="delete",
  1223. operator=self.request.auth.name if self.request.auth else None,
  1224. object_id=qs.id,
  1225. module_name="出库批次"
  1226. )
  1227. return Response(serializer.data, status=200, headers=headers)
  1228. def add_batch_log(self, data, log_type, goods_qty):
  1229. choices_dict = dict(BatchOperateLogModel.BATCH_LOG_TYPE)
  1230. log_type_name = choices_dict.get(log_type, "未知类型")
  1231. try:
  1232. # 获取 BoundBatchModel 实例
  1233. batch_obj = BoundBatchModel.objects.get(id=data['batch_number'])
  1234. except BoundBatchModel.DoesNotExist:
  1235. # 处理批次不存在的情况(如记录日志或抛出异常)
  1236. return False
  1237. log_data = {
  1238. 'batch_id': batch_obj, # 关键修复:传入实例对象,而不是 batch_obj.id
  1239. 'log_type': log_type,
  1240. 'log_date': timezone.now().strftime('%Y-%m-%d-%H:%M'), # 直接格式化时间,无需转字符串
  1241. 'goods_code': data['goods_code'],
  1242. 'goods_desc': data['goods_desc'],
  1243. 'goods_qty': data['goods_qty'],
  1244. 'log_content': f"{log_type_name} {goods_qty}件",
  1245. 'creater': data['creater'],
  1246. 'openid': data['openid'],
  1247. 'is_delete': False,
  1248. # 注意:create_time 和 update_time 由模型的 auto_now_add 和 auto_now 自动处理,无需手动赋值
  1249. }
  1250. # 创建日志记录
  1251. BatchOperateLogModel.objects.create(**log_data)
  1252. return True
  1253. # 批次操作日志类视图
  1254. class BoundBatchLogViewSet(viewsets.ModelViewSet):
  1255. """
  1256. retrieve:
  1257. Response a data list(get)
  1258. list:
  1259. Response a data list(all)
  1260. delete:
  1261. Delete a data line(delete)
  1262. """
  1263. # authentication_classes = [] # 禁用所有认证类
  1264. # permission_classes = [AllowAny] # 允许任意访问
  1265. pagination_class = MyPageNumberPagination
  1266. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  1267. ordering_fields = ['id', "create_time", "update_time", ]
  1268. filter_class = BatchlogFilter
  1269. def get_queryset(self):
  1270. return BatchOperateLogModel.objects.filter( is_delete=False)
  1271. def get_serializer_class(self):
  1272. if self.action in ['list', 'destroy','retrieve']:
  1273. return BatchLogGetSerializer
  1274. else:
  1275. return self.http_method_not_allowed(request=self.request)
  1276. def destroy(self, request, pk):
  1277. qs = self.get_object()
  1278. qs.is_delete = True
  1279. qs.save()
  1280. log_success_operation(
  1281. request=self.request,
  1282. operation_content=f"删除批次操作日志 ID:{qs.id}",
  1283. operation_level="delete",
  1284. operator=self.request.auth.name if self.request.auth else None,
  1285. object_id=qs.id,
  1286. module_name="批次操作日志"
  1287. )
  1288. serializer = self.get_serializer(qs, many=False)
  1289. headers = self.get_success_headers(serializer.data)
  1290. return Response(serializer.data, status=200, headers=headers)
  1291. # 托盘类视图
  1292. class BatchContainerAPIView(APIView):
  1293. """
  1294. post:
  1295. 返回批次对应的container列表
  1296. """
  1297. # authentication_classes = [] # 禁用所有认证类
  1298. # permission_classes = [AllowAny] # 允许任意访问
  1299. def post(self, request):
  1300. data = request.data
  1301. batch_id = data.get('batch_id')
  1302. from container.models import ContainerDetailModel
  1303. from bin.models import LocationContainerLink
  1304. container_detail_all = ContainerDetailModel.objects.filter(batch_id=batch_id, is_delete=False).exclude(status=3).all()
  1305. container_dict = {}
  1306. for container_detail in container_detail_all:
  1307. container_id = container_detail.container_id
  1308. link_obj = LocationContainerLink.objects.filter(container_id=container_id,is_active=True).first()
  1309. if container_id not in container_dict:
  1310. container_dict[container_id] = {
  1311. 'id': container_id,
  1312. 'goods_code': container_detail.goods_code,
  1313. 'goods_desc': container_detail.goods_desc,
  1314. 'container_code': container_detail.container.container_code,
  1315. 'current_location': container_detail.container.current_location,
  1316. 'goods_qty': container_detail.goods_qty-container_detail.goods_out_qty,
  1317. 'class':container_detail.goods_class,
  1318. 'location_c_number' : link_obj.location.c_number if link_obj else None,
  1319. 'location_max_capacity': link_obj.location.max_capacity if link_obj else None,
  1320. 'location_group': link_obj.location.location_group if link_obj else None,
  1321. 'location_code': link_obj.location.location_code if link_obj else None,
  1322. }
  1323. else:
  1324. container_dict[container_id]['goods_qty'] += container_detail.goods_qty-container_detail.goods_out_qty
  1325. container_dict = list(container_dict.values())
  1326. return_data = {'code': 200,'msg': 'Success Create', 'data': container_dict}
  1327. log_success_operation(
  1328. request=self.request,
  1329. operation_content=f"查询批次对应托盘 ID:{batch_id} 列表",
  1330. operation_level="view",
  1331. operator=self.request.auth.name if self.request.auth else None,
  1332. object_id=batch_id,
  1333. module_name="批次对应托盘"
  1334. )
  1335. return Response(return_data)