From 4188bae0fc714652b497a8c384f7dbc8156aa421 Mon Sep 17 00:00:00 2001 From: colaftc Date: Tue, 9 Dec 2025 16:34:46 +0800 Subject: [PATCH] fix: query param of api_man --- api_man/serializers.py | 6 +++ api_man/views.py | 22 ++++++++-- api_v1/tasks.py | 21 ++++++++- .../migrations/0020_product_mdy_image_url.py | 18 ++++++++ .../migrations/0021_alter_product_unit.py | 18 ++++++++ ...022_product_pieces_product_segment_size.py | 23 ++++++++++ basic_info/models.py | 41 ++++++++++++++++++ celerybeat-schedule | Bin 12288 -> 12288 bytes celerybeat-schedule-shm | Bin 32768 -> 32768 bytes celerybeat-schedule-wal | Bin 65952 -> 82432 bytes flower/utils.py | 4 ++ ...il_unit_alter_stockfreeze_unit_and_more.py | 33 ++++++++++++++ 12 files changed, 181 insertions(+), 5 deletions(-) create mode 100644 basic_info/migrations/0020_product_mdy_image_url.py create mode 100644 basic_info/migrations/0021_alter_product_unit.py create mode 100644 basic_info/migrations/0022_product_pieces_product_segment_size.py create mode 100644 stock/migrations/0009_alter_stockchangedetail_unit_alter_stockfreeze_unit_and_more.py diff --git a/api_man/serializers.py b/api_man/serializers.py index 0aeb6fd..4f9134b 100644 --- a/api_man/serializers.py +++ b/api_man/serializers.py @@ -67,6 +67,12 @@ class ProductSerializer(BaseSerializer): - 如果有图片:返回完整 URL(七牛云 CDN 地址) - 如果没有图片:返回 None """ + primary_url_resolver = getattr(obj, 'get_primary_image_url', None) + if callable(primary_url_resolver): + resolved = primary_url_resolver() + if resolved: + return resolved + if obj.image: request = self.context.get('request') if request: diff --git a/api_man/views.py b/api_man/views.py index a92b14c..f99058a 100644 --- a/api_man/views.py +++ b/api_man/views.py @@ -2,6 +2,8 @@ from rest_framework import viewsets from rest_framework.exceptions import PermissionDenied from rest_framework.pagination import LimitOffsetPagination from rest_framework.permissions import IsAuthenticated, DjangoModelPermissions +from rest_framework.decorators import action +from rest_framework.response import Response from django_filters.rest_framework import DjangoFilterBackend from django_filters import rest_framework as dj_filters @@ -68,11 +70,18 @@ class BasicInfoFilterMixin: def filter_queryset(self, queryset): field = self._detect_filter_field() model = getattr(getattr(self, 'queryset', None), 'model', None) - if not field or not model: - return super().filter_queryset(queryset) + if field and model: + self.filterset_class = self._build_filterset_class(model, field) - self.filterset_class = self._build_filterset_class(model, field) - return super().filter_queryset(queryset) + qs = super().filter_queryset(queryset) + + # 统一按创建时间倒序排序(如果模型存在 created_at 字段) + model = getattr(qs, 'model', model) + if model: + field_names = {f.name for f in model._meta.get_fields() if hasattr(f, 'name')} + if 'created_at' in field_names: + return qs.order_by('-created_at') + return qs class BaseViewSet(BasicInfoFilterMixin, viewsets.ModelViewSet): @@ -106,6 +115,11 @@ class QuickInputViewSet(BasicInfoFilterMixin, viewsets.ModelViewSet): qs = qs.filter(group=group) return qs + @action(detail=False, methods=['get'], url_path='groups') + def groups(self, request): + groups = serializers.basic_models.QuickInput.objects.values_list('group', flat=True).distinct() + return Response(groups) + class ProductViewSet(BaseViewSet): queryset = serializers.basic_models.Product.objects diff --git a/api_v1/tasks.py b/api_v1/tasks.py index bcbd8ca..276f646 100644 --- a/api_v1/tasks.py +++ b/api_v1/tasks.py @@ -139,6 +139,7 @@ def _map_unit(unit_label: str | None) -> int: '米': basic_models.ProductUnitEnum.METER, '码': basic_models.ProductUnitEnum.YARD, '公斤': basic_models.ProductUnitEnum.KG, + '段': basic_models.ProductUnitEnum.SEGMENT, } return mapping.get(unit_label, basic_models.ProductUnitEnum.METER) @@ -152,6 +153,19 @@ def _ensure_decimal(value: str | None): return None +def _ensure_int(value): + if value is None: + return None + try: + return int(value) + except (TypeError, ValueError, InvalidOperation): + # 有些值可能是 Decimal 或字符串数字 + try: + return int(Decimal(str(value))) + except Exception: + return None + + def _run_fetch(page: int, page_size: int): return asyncio.run(fetch_products_from_mingdaoyun(page=page, page_size=page_size)) @@ -175,6 +189,12 @@ def _upsert_product(product_data, merchant, category): width_decimal = _ensure_decimal(product_data.width) if width_decimal is not None: defaults['width_size'] = width_decimal + pieces_int = _ensure_int(product_data.pieces) + if pieces_int is not None: + defaults['pieces'] = pieces_int + segment_int = _ensure_int(product_data.segment_size) + if segment_int is not None: + defaults['segment_size'] = segment_int product_obj, created = basic_models.Product.objects.get_or_create( merchant=merchant, @@ -387,4 +407,3 @@ def sync_mdy_customers(self, page_size: int = 300, max_pages: int | None = None, } logger.info('明道云客户同步完成: %s', payload) return payload - diff --git a/basic_info/migrations/0020_product_mdy_image_url.py b/basic_info/migrations/0020_product_mdy_image_url.py new file mode 100644 index 0000000..d27c80b --- /dev/null +++ b/basic_info/migrations/0020_product_mdy_image_url.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.8 on 2025-12-09 06:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('basic_info', '0019_customer_mdy_fields'), + ] + + operations = [ + migrations.AddField( + model_name='product', + name='mdy_image_url', + field=models.URLField(blank=True, help_text='来自明道云的图片链接', max_length=500, null=True, verbose_name='明道云图片URL'), + ), + ] diff --git a/basic_info/migrations/0021_alter_product_unit.py b/basic_info/migrations/0021_alter_product_unit.py new file mode 100644 index 0000000..53855a7 --- /dev/null +++ b/basic_info/migrations/0021_alter_product_unit.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.8 on 2025-12-09 07:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('basic_info', '0020_product_mdy_image_url'), + ] + + operations = [ + migrations.AlterField( + model_name='product', + name='unit', + field=models.IntegerField(choices=[(1, '米'), (2, '码'), (3, '公斤'), (4, '段')], default=1, verbose_name='单位'), + ), + ] diff --git a/basic_info/migrations/0022_product_pieces_product_segment_size.py b/basic_info/migrations/0022_product_pieces_product_segment_size.py new file mode 100644 index 0000000..58216b0 --- /dev/null +++ b/basic_info/migrations/0022_product_pieces_product_segment_size.py @@ -0,0 +1,23 @@ +# Generated by Django 5.2.8 on 2025-12-09 07:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('basic_info', '0021_alter_product_unit'), + ] + + operations = [ + migrations.AddField( + model_name='product', + name='pieces', + field=models.IntegerField(blank=True, null=True, verbose_name='件数'), + ), + migrations.AddField( + model_name='product', + name='segment_size', + field=models.IntegerField(blank=True, null=True, verbose_name='一段尺寸'), + ), + ] diff --git a/basic_info/models.py b/basic_info/models.py index 68e1388..478d0eb 100644 --- a/basic_info/models.py +++ b/basic_info/models.py @@ -1,4 +1,5 @@ from decimal import Decimal +import json from django.db import models from flower.common import ModelBase @@ -36,6 +37,7 @@ class ProductUnitEnum(models.IntegerChoices): METER = 1, '米' YARD = 2, '码' KG = 3, '公斤' + SEGMENT = 4, '段' class EmployeeTypeEnum(models.TextChoices): @@ -226,6 +228,8 @@ class Product(ModelBase): human_id = models.CharField(max_length=100, verbose_name='产品编号', blank=True) color = models.CharField(max_length=50, blank=True, null=True, verbose_name='颜色') width_size = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True, verbose_name='宽幅') + pieces = models.IntegerField(blank=True, null=True, verbose_name='件数') + segment_size = models.IntegerField(blank=True, null=True, verbose_name='一段尺寸') single_price_in = models.DecimalField( max_digits=10, decimal_places=2, @@ -253,6 +257,13 @@ class Product(ModelBase): null=True, verbose_name='产品图片', ) + mdy_image_url = models.URLField( + max_length=500, + blank=True, + null=True, + verbose_name='明道云图片URL', + help_text='来自明道云的图片链接', + ) minimum_quantity = models.IntegerField(null=True, blank=True, verbose_name='最低库存量') transform_rate = models.DecimalField( max_digits=10, @@ -267,6 +278,36 @@ class Product(ModelBase): help_text='标记该产品是否通过明道云接口同步', ) + def get_primary_image_url(self): + """ + 优先返回 mdy_image_url;若为空则尝试从 description JSON 中提取 original_file_full_path。 + """ + if self.mdy_image_url: + return self.mdy_image_url + + if not self.description: + return None + + try: + payload = json.loads(self.description) if isinstance(self.description, str) else self.description + except (TypeError, ValueError, json.JSONDecodeError): + return None + + if isinstance(payload, dict): + items = [payload] + elif isinstance(payload, list): + items = payload + else: + return None + + for item in items: + if not isinstance(item, dict): + continue + url = item.get('original_file_full_path') or item.get('DownloadUrl') + if url: + return url + return None + def __str__(self): return self.name diff --git a/celerybeat-schedule b/celerybeat-schedule index 5cff2063ffedbe0eb4289c25b3d159ed0d576edd..fe368d5673d5768823739309b2f69120da681fbb 100644 GIT binary patch delta 132 zcmZojXh_&_SB9xsYV&=WD~$XC3_zecB}1%jO3)N<*U5+EmVm{WN?>A2lS}3Gn3@?T zFO^poZ>ncj5-BZ7j!(@?Ov*`3X<+TpQ*5f+d|&=2zXoGHJF@^&Nfl!q53>|gYF)S}|d{Ji*h#s&Z&!72Oz delta 132 zcmZojXh_&_SB9yna`SzeD~$Yn3_xHoB}1%jO3)N<=gEiUmVm{WcEiLJCYQ?VF*RCE zUMjCF-e}3JBvM+E9G{w(n3R*6(!koGr`Tw*`M&&5ehr45?92j8B~=VNc$lS_Qu9iR bGE<8gD+QR9nBwEhQj3Z+^Yh~48Fm2xWf3aW diff --git a/celerybeat-schedule-shm b/celerybeat-schedule-shm index 8e1cc8fe3e30ab26000cfa7e0dcf3900f84aab70..d6ecfac27306030e4f3e5690c17794becb1f7667 100644 GIT binary patch delta 168 zcmZo@U}|V!s+V}A%K!pgK+MR%ARq#ynSr=gv%&Jlv7jAOc3OO$vRLd;@9o9QSpNj9 zBUL@nC@=t-`yUBFg(uc~PFx_c@q~)&hwd(r%X3aEL~-wZ<+X8dj9b@({FXI sCsjSrC@=t-`yUBFg(uc~ZoHx5I`MYufB^)i?6mkgWwF?yQ}TP3yU3Y8017c7i>E)`F8lk~ z+KU@4?(#4-OKleCy~4;3)~Y!rL#%B|&=hai$-nrPfW??fU}8#>&tA}Hl%9NcL$UzO zij^#^olDl~OK*1A_>Ny|mH@L7Q+#|`YEf}!eqMY$<7{-Rmzhn^-xm8xcyopVnRZ(+ zHOovE7tn+|Vb?6<=A4~@r9dY<JIv5ZZorozp9}%|9Bb>{xk} z=>Je>y|LSD(-l<5&PPrLgdHo(#91*zZ{N+$8VkvHtOB-Vfa=&84BKWDT;kz|ICcRv z8Ib7M6F`YP|yAVCY8&cnF(!RC&K z_jnkUNK3NByLEPc&*b`)g=t{7+F=dYMd(Qu!>vEn+G2L8FWTJx@IDWtGOe7-qYufB^(1maa0;w@m!aI7Y%}|Qch}018aw#Vxz@maRE&MnEhL}O|t(f?6_fbfWQrY2Zo*O%mPd$ zRSY|Ln5CFf^Gb>`Q;QiZ1(=nX;^WIwi;6Sz^Wx(ftI%CLn|tco)laMiH)kkN*||;C zD9)9ZUbg+R{n90<&Sk77&AB<@ImID&rJXm=a3I&YDq=NQ0vFA-5#Q^|<=fUD1-kYo zzkLlPY)N&jId81mtJ&w0H=jrVIkuLJq(GEoEpM*aH&0#rEgr|#5=;ovTdy>}a`&#- z{G)+v$1>JTW)iT2r%?SR%Us2_IG#gwR~>SU6L!}#&3RMA4%`gbtg(<>cO}DJRB-L= znumr#d_Why~28MnXL!; nlOIp=e!02h;XNKkMp6 diff --git a/flower/utils.py b/flower/utils.py index c401c46..dd40e85 100644 --- a/flower/utils.py +++ b/flower/utils.py @@ -11,6 +11,8 @@ class Product(BaseModel): uid: str rowid: str name: str + pieces: int | None + segment_size: int | None unit: str | None color: str | None width: str | None = None @@ -173,9 +175,11 @@ class MingDaoYunClient: product_type_map = { 'name': '668caa5eb80969563ecaee7d', + 'pieces': '689c5aa37c175367b29fcb87', 'color': '66ed3b9ae01d5599bdb45f6d', 'uid': '668caa5eb80969563ecaee7c', 'unit': '668e2ee2ca758c8cc5d0dc1d', + 'segment_size': '668e2194790c0e04058b4f3c', 'width': '668e2194790c0e04058b4f3c', 'detail_str': '668caa5eb80969563ecaee7e', 'created_at': 'ctime', diff --git a/stock/migrations/0009_alter_stockchangedetail_unit_alter_stockfreeze_unit_and_more.py b/stock/migrations/0009_alter_stockchangedetail_unit_alter_stockfreeze_unit_and_more.py new file mode 100644 index 0000000..cb7f2e5 --- /dev/null +++ b/stock/migrations/0009_alter_stockchangedetail_unit_alter_stockfreeze_unit_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 5.2.8 on 2025-12-09 07:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('stock', '0008_alter_stockchangerecord_source_type'), + ] + + operations = [ + migrations.AlterField( + model_name='stockchangedetail', + name='unit', + field=models.IntegerField(choices=[(1, '米'), (2, '码'), (3, '公斤'), (4, '段')], verbose_name='单位'), + ), + migrations.AlterField( + model_name='stockfreeze', + name='unit', + field=models.IntegerField(choices=[(1, '米'), (2, '码'), (3, '公斤'), (4, '段')], verbose_name='单位'), + ), + migrations.AlterField( + model_name='stocksnapshot', + name='unit', + field=models.IntegerField(choices=[(1, '米'), (2, '码'), (3, '公斤'), (4, '段')], verbose_name='单位'), + ), + migrations.AlterField( + model_name='transferorderitem', + name='unit', + field=models.IntegerField(choices=[(1, '米'), (2, '码'), (3, '公斤'), (4, '段')], verbose_name='单位'), + ), + ]