diff --git a/bug-fix-20251122-stateflow.md b/bug-fix-20251122-stateflow.md new file mode 100644 index 0000000..bee2257 --- /dev/null +++ b/bug-fix-20251122-stateflow.md @@ -0,0 +1,56 @@ +# Bug 修复报告 + +## 基本信息 +- 日期:2025-11-22 +- 模块:stateflow(流程状态管理) +- 修复者:Cline + +--- + +## 问题描述 +API `GET /api/v1/stateflow/business-objects/xxx/state-logs/` 无法获取已撤销状态的工艺参数。 + +**具体表现**:当一个状态流转记录被撤销后,通过该API获取状态日志列表时,已撤销状态记录的工艺参数会被过滤掉,导致参数丢失。 + +**影响范围**: +1. 使用 `GET /api/v1/stateflow/business-objects/xxx/state-logs/` API 获取包含已撤销状态的日志列表 +2. 依赖这些日志进行审计、回溯或数据分析的功能 + +--- + +## 根本原因 +在 `StateFlowRecordWithParametersSerializer` 中调用 `get_all_parameters_summary()` 时未传递 `include_cancelled=True` 参数,导致已撤销状态的参数被 `get_all_parameters_summary()` 方法内部逻辑过滤掉。 + +--- + +## 修复方案 +修改 `/home/f/coding/flower/stateflow/serializers.py` 中的 `get_parameters_summary` 方法: + +**修改前**: +```python +def get_parameters_summary(self, obj): + """获取参数摘要""" + return obj.get_all_parameters_summary() +``` + +**修改后**: +```python +def get_parameters_summary(self, obj): + """获取参数摘要,包含已撤销状态的参数""" + return obj.get_all_parameters_summary(include_cancelled=True) +``` + +--- + +## 测试验证 +1. **创建测试用例**:在 `/home/f/coding/flower/stateflow/tests/test_business_object_api.py` 中创建 `test_get_state_logs_api_cancelled_state_with_parameters` 测试用例,用于复现和验证bug。 + +2. **验证修复**: + - 运行测试用例 `test_get_state_logs_api_cancelled_state_with_parameters` + - 修复前测试失败,修复后测试通过 + - 验证已撤销状态的工艺参数能够正确返回 + +--- + +## 总结 +该修复确保了已撤销状态的工艺参数不会丢失,与未撤销状态的处理方式保持一致,符合业务需求。修复仅涉及一行代码的修改,但确保了数据完整性和API的一致性。 diff --git a/printing/models.py b/printing/models.py index 97db5c6..d4e8177 100644 --- a/printing/models.py +++ b/printing/models.py @@ -54,7 +54,7 @@ class PlateOrder(ModelBase): basic_models.Customer, on_delete=models.PROTECT, related_name='plate_orders', - verbose_name='客户' + verbose_name='客户', ) area = models.CharField(max_length=255, blank=True, null=True, verbose_name='区域') default_address = models.CharField(max_length=500, blank=True, null=True, verbose_name='默认地址') diff --git a/pyproject.toml b/pyproject.toml index c3260bb..9c49d94 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,4 +23,5 @@ dependencies = [ [dependency-groups] dev = [ "psycopg[binary]>=3.2.12", + "pytest>=9.0.1", ] diff --git a/stateflow/serializers.py b/stateflow/serializers.py index a51999f..e5aef13 100644 --- a/stateflow/serializers.py +++ b/stateflow/serializers.py @@ -232,8 +232,8 @@ class StateFlowRecordWithParametersSerializer(serializers.ModelSerializer): read_only_fields = ['id', 'completed_at', 'is_cancelled', 'cancelled_at'] def get_parameters_summary(self, obj): - """获取参数摘要""" - return obj.get_all_parameters_summary() + """获取参数摘要,包含已撤销状态的参数""" + return obj.get_all_parameters_summary(include_cancelled=True) class BusinessObjectListSerializer(serializers.ModelSerializer): diff --git a/stateflow/tests/test_business_object_api.py b/stateflow/tests/test_business_object_api.py index 9f784a8..f1c7480 100644 --- a/stateflow/tests/test_business_object_api.py +++ b/stateflow/tests/test_business_object_api.py @@ -597,6 +597,38 @@ class BusinessObjectAPITestCase(TestCase): self.assertLessEqual(time1, time2) self.assertLessEqual(time2, time3) + def test_get_state_logs_api_cancelled_state_with_parameters(self): + """测试获取已撤销状态的工艺参数(bug修复验证)""" + # 推进并提供参数 + self.client.post( + f'/api/v1/stateflow/business-objects/{self.business_object.id}/advance/', + {'parameters': {'temperature': '25.5', 'humidity': '60%'}}, + format='json' + ) + + # 回退(撤销该状态) + self.client.post(f'/api/v1/stateflow/business-objects/{self.business_object.id}/step_back/') + + # 使用 include_cancelled=true 和 include_parameters=true 获取状态记录 + response = self.client.get( + f'/api/v1/stateflow/business-objects/{self.business_object.id}/state-logs/?include_cancelled=true&include_parameters=true' + ) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + # 验证返回2条记录(撤销后原来的记录也在,只是标记为已撤销) + self.assertEqual(response.data['count'], 1) + + # 验证记录包含工艺参数 + logs = response.data['state_logs'] + self.assertEqual(len(logs), 1) + + state_log = logs[0] + self.assertTrue(state_log['is_cancelled']) + self.assertIn('parameters_summary', state_log) + self.assertEqual(state_log['parameters_summary']['temperature'], '25.5') + self.assertEqual(state_log['parameters_summary']['humidity'], '60%') + def test_advance_when_already_completed(self): """测试流程已完成时尝试推进(预期错误)""" # 完成所有状态 diff --git a/uv.lock b/uv.lock index d753ffb..b54c9be 100644 --- a/uv.lock +++ b/uv.lock @@ -231,6 +231,7 @@ dependencies = [ [package.dev-dependencies] dev = [ { name = "psycopg", extra = ["binary"] }, + { name = "pytest" }, ] [package.metadata] @@ -251,7 +252,10 @@ requires-dist = [ ] [package.metadata.requires-dev] -dev = [{ name = "psycopg", extras = ["binary"], specifier = ">=3.2.12" }] +dev = [ + { name = "psycopg", extras = ["binary"], specifier = ">=3.2.12" }, + { name = "pytest", specifier = ">=9.0.1" }, +] [[package]] name = "h11" @@ -280,6 +284,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/59/91/aa6bde563e0085a02a435aa99b49ef75b0a4b062635e606dab23ce18d720/inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2", size = 9454, upload-time = "2020-08-22T08:16:27.816Z" }, ] +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + [[package]] name = "jsonschema" version = "4.25.1" @@ -316,6 +329,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/70/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl", hash = "sha256:b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c", size = 107678, upload-time = "2025-11-03T19:51:13.887Z" }, ] +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + [[package]] name = "pillow" version = "12.0.0" @@ -349,6 +371,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c1/70/6b41bdcddf541b437bbb9f47f94d2db5d9ddef6c37ccab8c9107743748a4/pillow-12.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:99353a06902c2e43b43e8ff74ee65a7d90307d82370604746738a1e0661ccca7", size = 2525630, upload-time = "2025-10-15T18:23:57.149Z" }, ] +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + [[package]] name = "psycopg" version = "3.2.12" @@ -427,6 +458,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/ac/9fc61b4f9d079482a290afe8d206b8f490e9fd32d4fc03ed4fc698214e01/pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0", size = 1973897, upload-time = "2025-10-14T10:22:13.444Z" }, ] +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + [[package]] name = "pyjwt" version = "2.10.1" @@ -436,6 +476,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" }, ] +[[package]] +name = "pytest" +version = "9.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/56/f013048ac4bc4c1d9be45afd4ab209ea62822fb1598f40687e6bf45dcea4/pytest-9.0.1.tar.gz", hash = "sha256:3e9c069ea73583e255c3b21cf46b8d3c56f6e3a1a8f6da94ccb0fcf57b9d73c8", size = 1564125, upload-time = "2025-11-12T13:05:09.333Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/8b/6300fb80f858cda1c51ffa17075df5d846757081d11ab4aa35cef9e6258b/pytest-9.0.1-py3-none-any.whl", hash = "sha256:67be0030d194df2dfa7b556f2e56fb3c3315bd5c8822c6951162b92b32ce7dad", size = 373668, upload-time = "2025-11-12T13:05:07.379Z" }, +] + [[package]] name = "pyyaml" version = "6.0.3"