style: ruff format 对本轮修改文件统一格式
This commit is contained in:
+13
-2
@@ -536,7 +536,13 @@ def build_formula_workbook(ds, member, export_context, output):
|
|||||||
"导入确认的产品毛利率表",
|
"导入确认的产品毛利率表",
|
||||||
"仅 GrossSource=lookup 的行参与毛利计算;0.2 表示 20%",
|
"仅 GrossSource=lookup 的行参与毛利计算;0.2 表示 20%",
|
||||||
),
|
),
|
||||||
("ListAmount", "原价金额", "金额", source_name("list_amount"), "用于扣率与原价客单价;缺失则相关指标不可用"),
|
(
|
||||||
|
"ListAmount",
|
||||||
|
"原价金额",
|
||||||
|
"金额",
|
||||||
|
source_name("list_amount"),
|
||||||
|
"用于扣率与原价客单价;缺失则相关指标不可用",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
for row in [
|
for row in [
|
||||||
["用途", "公式分析工作簿;本地修改不回写系统。"],
|
["用途", "公式分析工作簿;本地修改不回写系统。"],
|
||||||
@@ -762,7 +768,12 @@ def process_formula_export(export_id):
|
|||||||
job.save(update_fields=["path", "status", "error", "finished_at", "expires_at"])
|
job.save(update_fields=["path", "status", "error", "finished_at", "expires_at"])
|
||||||
return stats
|
return stats
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
job.status, job.error, job.finished_at, job.expires_at = "failed", str(exc)[:500], timezone.now(), None
|
job.status, job.error, job.finished_at, job.expires_at = (
|
||||||
|
"failed",
|
||||||
|
str(exc)[:500],
|
||||||
|
timezone.now(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
# 只清理本次登记且确认位于受控目录内的文件;目录被换成符号链接时拒绝删除。
|
# 只清理本次登记且确认位于受控目录内的文件;目录被换成符号链接时拒绝删除。
|
||||||
leftover = [path for path in created if _unlink_created_file(path) == "blocked"]
|
leftover = [path for path in created if _unlink_created_file(path) == "blocked"]
|
||||||
if leftover:
|
if leftover:
|
||||||
|
|||||||
@@ -249,7 +249,9 @@ def formula_definitions(request, dataset_id):
|
|||||||
raise APIError("定义保存发生并发冲突,请重试", 409) from exc
|
raise APIError("定义保存发生并发冲突,请重试", 409) from exc
|
||||||
created = True
|
created = True
|
||||||
# 审计与定义变更在同一事务内提交;审计失败时变更一并回滚。
|
# 审计与定义变更在同一事务内提交;审计失败时变更一并回滚。
|
||||||
audit(request, "formula_definition.save", item.pk, {"dataset": str(ds.pk), "name": definition["name"]})
|
audit(
|
||||||
|
request, "formula_definition.save", item.pk, {"dataset": str(ds.pk), "name": definition["name"]}
|
||||||
|
)
|
||||||
return JsonResponse(
|
return JsonResponse(
|
||||||
{
|
{
|
||||||
"id": str(item.pk),
|
"id": str(item.pk),
|
||||||
|
|||||||
@@ -23,9 +23,7 @@ class Command(BaseCommand):
|
|||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
while True:
|
while True:
|
||||||
jobs, files, orphans = self.clean_expired()
|
jobs, files, orphans = self.clean_expired()
|
||||||
self.stdout.write(
|
self.stdout.write(f"Expired {jobs} jobs; removed {files} files; reclaimed {orphans} orphan files")
|
||||||
f"Expired {jobs} jobs; removed {files} files; reclaimed {orphans} orphan files"
|
|
||||||
)
|
|
||||||
if not options["loop"]:
|
if not options["loop"]:
|
||||||
return
|
return
|
||||||
time.sleep(max(60, options["interval"]))
|
time.sleep(max(60, options["interval"]))
|
||||||
|
|||||||
@@ -4,15 +4,14 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('finance', '0004_formuladefinition'),
|
("finance", "0004_formuladefinition"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='formuladefinition',
|
model_name="formuladefinition",
|
||||||
name='revision',
|
name="revision",
|
||||||
field=models.PositiveIntegerField(default=1),
|
field=models.PositiveIntegerField(default=1),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -98,9 +98,7 @@ def test_formula_workbook_exposes_custom_metric_as_recalculable_column(formula_d
|
|||||||
assert custom["B1"].value == "收入成本差(本期)"
|
assert custom["B1"].value == "收入成本差(本期)"
|
||||||
assert custom["B2"].value == f"='分析汇总'!C{metric_row}"
|
assert custom["B2"].value == f"='分析汇总'!C{metric_row}"
|
||||||
custom_note = next(
|
custom_note = next(
|
||||||
str(row[1])
|
str(row[1]) for row in workbook["使用说明"].iter_rows(values_only=True) if row[0] == "收入成本差"
|
||||||
for row in workbook["使用说明"].iter_rows(values_only=True)
|
|
||||||
if row[0] == "收入成本差"
|
|
||||||
)
|
)
|
||||||
assert "含义:实际收入减成本" in custom_note
|
assert "含义:实际收入减成本" in custom_note
|
||||||
assert "范围:按所选分组及本期、基期计算" in custom_note
|
assert "范围:按所选分组及本期、基期计算" in custom_note
|
||||||
|
|||||||
@@ -54,10 +54,21 @@ def test_to_decimal_evaluator_golden_samples():
|
|||||||
{"operator": "divide", "left": {"metric": "revenue"}, "right": {"metric": "customers"}}, values
|
{"operator": "divide", "left": {"metric": "revenue"}, "right": {"metric": "customers"}}, values
|
||||||
) == Decimal("999999999999.99") / Decimal("7")
|
) == Decimal("999999999999.99") / Decimal("7")
|
||||||
# 除零与缺失指标返回 None(对应 Excel #N/A)
|
# 除零与缺失指标返回 None(对应 Excel #N/A)
|
||||||
assert to_decimal({"operator": "divide", "left": {"metric": "revenue"}, "right": {"number": 0}}, values) is None
|
assert (
|
||||||
assert to_decimal({"operator": "add", "left": {"metric": "cost"}, "right": {"metric": "customers"}}, {**values, "customers": None}) is None
|
to_decimal({"operator": "divide", "left": {"metric": "revenue"}, "right": {"number": 0}}, values)
|
||||||
|
is None
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
to_decimal(
|
||||||
|
{"operator": "add", "left": {"metric": "cost"}, "right": {"metric": "customers"}},
|
||||||
|
{**values, "customers": None},
|
||||||
|
)
|
||||||
|
is None
|
||||||
|
)
|
||||||
# 负数与常数组合
|
# 负数与常数组合
|
||||||
assert to_decimal({"operator": "subtract", "left": {"number": -2.5}, "right": {"metric": "cost"}}, values) == Decimal("-2.5") - Decimal("0.0000001")
|
assert to_decimal(
|
||||||
|
{"operator": "subtract", "left": {"number": -2.5}, "right": {"metric": "cost"}}, values
|
||||||
|
) == Decimal("-2.5") - Decimal("0.0000001")
|
||||||
|
|
||||||
|
|
||||||
def test_reconcile_uses_independent_tolerances_per_metric_kind(workspace):
|
def test_reconcile_uses_independent_tolerances_per_metric_kind(workspace):
|
||||||
@@ -72,7 +83,16 @@ def test_reconcile_uses_independent_tolerances_per_metric_kind(workspace):
|
|||||||
workbook = load_workbook(BytesIO(output.getvalue()), data_only=False)
|
workbook = load_workbook(BytesIO(output.getvalue()), data_only=False)
|
||||||
sheet = workbook["对账检查"]
|
sheet = workbook["对账检查"]
|
||||||
rows = list(sheet.iter_rows(values_only=True))
|
rows = list(sheet.iter_rows(values_only=True))
|
||||||
assert list(rows[0]) == ["指标", "分组", "期间侧", "服务器 Decimal 参考", "公式结果", "差异", "容差", "状态"]
|
assert list(rows[0]) == [
|
||||||
|
"指标",
|
||||||
|
"分组",
|
||||||
|
"期间侧",
|
||||||
|
"服务器 Decimal 参考",
|
||||||
|
"公式结果",
|
||||||
|
"差异",
|
||||||
|
"容差",
|
||||||
|
"状态",
|
||||||
|
]
|
||||||
|
|
||||||
def find(metric, side="本期"):
|
def find(metric, side="本期"):
|
||||||
return next(row for row in rows[1:] if row[0] == metric and row[2] == side)
|
return next(row for row in rows[1:] if row[0] == metric and row[2] == side)
|
||||||
@@ -84,7 +104,9 @@ def test_reconcile_uses_independent_tolerances_per_metric_kind(workspace):
|
|||||||
assert find("ROI")[6] == pytest.approx(0.0001)
|
assert find("ROI")[6] == pytest.approx(0.0001)
|
||||||
assert find("客单价")[6] == pytest.approx(0.0001)
|
assert find("客单价")[6] == pytest.approx(0.0001)
|
||||||
# 状态公式按容差列比较(<=G),不再统一 <0.01
|
# 状态公式按容差列比较(<=G),不再统一 <0.01
|
||||||
status = next(cell.value for cell in sheet[2] if isinstance(cell.value, str) and cell.value.startswith("=IF(D"))
|
status = next(
|
||||||
|
cell.value for cell in sheet[2] if isinstance(cell.value, str) and cell.value.startswith("=IF(D")
|
||||||
|
)
|
||||||
assert "<=G2" in status
|
assert "<=G2" in status
|
||||||
# 参数页写明对账规则,容差调整需财务批准
|
# 参数页写明对账规则,容差调整需财务批准
|
||||||
parameters = {str(row[0]): row for row in workbook["参数与口径"].iter_rows(values_only=True) if row[0]}
|
parameters = {str(row[0]): row for row in workbook["参数与口径"].iter_rows(values_only=True) if row[0]}
|
||||||
@@ -208,8 +230,7 @@ def test_reconcile_passes_in_libreoffice_with_per_metric_tolerances(workspace, t
|
|||||||
assert result.returncode == 0, result.stderr or result.stdout
|
assert result.returncode == 0, result.stderr or result.stdout
|
||||||
values = load_workbook(output / workbook_path.name, data_only=True)
|
values = load_workbook(output / workbook_path.name, data_only=True)
|
||||||
statuses = [
|
statuses = [
|
||||||
(row[0], row[1], row[2], row[7])
|
(row[0], row[1], row[2], row[7]) for row in values["对账检查"].iter_rows(min_row=2, values_only=True)
|
||||||
for row in values["对账检查"].iter_rows(min_row=2, values_only=True)
|
|
||||||
]
|
]
|
||||||
assert statuses
|
assert statuses
|
||||||
assert all(status in ("通过", "无参考") for *_head, status in statuses), statuses
|
assert all(status in ("通过", "无参考") for *_head, status in statuses), statuses
|
||||||
|
|||||||
@@ -88,26 +88,21 @@ def test_status_and_download_deny_revoked_viewer(workspace, settings):
|
|||||||
job.save(update_fields=["path"])
|
job.save(update_fields=["path"])
|
||||||
|
|
||||||
assert client.get(f"/api/datasets/{ds.pk}/formula-exports/{job.pk}").status_code == 200
|
assert client.get(f"/api/datasets/{ds.pk}/formula-exports/{job.pk}").status_code == 200
|
||||||
assert (
|
assert client.get(f"/api/datasets/{ds.pk}/formula-exports/{job.pk}/download").status_code == 200
|
||||||
client.get(f"/api/datasets/{ds.pk}/formula-exports/{job.pk}/download").status_code == 200
|
|
||||||
)
|
|
||||||
|
|
||||||
member.role = "viewer"
|
member.role = "viewer"
|
||||||
member.save(update_fields=["role"])
|
member.save(update_fields=["role"])
|
||||||
assert client.get(f"/api/datasets/{ds.pk}/formula-exports/{job.pk}").status_code == 403
|
assert client.get(f"/api/datasets/{ds.pk}/formula-exports/{job.pk}").status_code == 403
|
||||||
assert (
|
assert client.get(f"/api/datasets/{ds.pk}/formula-exports/{job.pk}/download").status_code == 403
|
||||||
client.get(f"/api/datasets/{ds.pk}/formula-exports/{job.pk}/download").status_code == 403
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_half_finished_export_cannot_be_downloaded(workspace, settings):
|
def test_half_finished_export_cannot_be_downloaded(workspace, settings):
|
||||||
ds, _, client, *_ = workspace
|
ds, _, client, *_ = workspace
|
||||||
for status, code in (("processing", 409), ("failed", 409), ("queued", 409)):
|
for status, code in (("processing", 409), ("failed", 409), ("queued", 409)):
|
||||||
job = _export_job(ds, status=status)
|
job = _export_job(ds, status=status)
|
||||||
assert (
|
assert client.get(f"/api/datasets/{ds.pk}/formula-exports/{job.pk}/download").status_code == code, (
|
||||||
client.get(f"/api/datasets/{ds.pk}/formula-exports/{job.pk}/download").status_code
|
status
|
||||||
== code
|
)
|
||||||
), status
|
|
||||||
succeeded = _export_job(
|
succeeded = _export_job(
|
||||||
ds,
|
ds,
|
||||||
status="succeeded",
|
status="succeeded",
|
||||||
@@ -115,18 +110,13 @@ def test_half_finished_export_cannot_be_downloaded(workspace, settings):
|
|||||||
finished_at=timezone.now(),
|
finished_at=timezone.now(),
|
||||||
expires_at=timezone.now() + timedelta(hours=1),
|
expires_at=timezone.now() + timedelta(hours=1),
|
||||||
)
|
)
|
||||||
assert (
|
assert client.get(f"/api/datasets/{ds.pk}/formula-exports/{succeeded.pk}/download").status_code == 410
|
||||||
client.get(f"/api/datasets/{ds.pk}/formula-exports/{succeeded.pk}/download").status_code
|
|
||||||
== 410
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_stale_processing_export_is_reclaimed_after_timeout(workspace):
|
def test_stale_processing_export_is_reclaimed_after_timeout(workspace):
|
||||||
ds, *_ = workspace
|
ds, *_ = workspace
|
||||||
job = _export_job(ds, status="processing")
|
job = _export_job(ds, status="processing")
|
||||||
FormulaExport.objects.filter(pk=job.pk).update(
|
FormulaExport.objects.filter(pk=job.pk).update(created_at=timezone.now() - timedelta(minutes=20))
|
||||||
created_at=timezone.now() - timedelta(minutes=20)
|
|
||||||
)
|
|
||||||
|
|
||||||
call_command("import_worker", once=True)
|
call_command("import_worker", once=True)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user