feat(business): exclude intercompany transfer orders

Allow operators to configure internal company names and identifiers, then apply the filter consistently across business templates and query routing. Preserve direct external-customer orders and provide explicit CLI and environment overrides for internal-flow audits.

Validation: python -m unittest tests.test_business_facts tests.test_wiki_skills tests.test_user_ops -v (25 tests)
This commit is contained in:
Codex
2026-07-10 05:40:54 +00:00
parent 7eb9b987ab
commit 4c5454da90
7 changed files with 416 additions and 34 deletions
+194
View File
@@ -1,4 +1,5 @@
import json
import os
import sqlite3
import tempfile
import unittest
@@ -599,6 +600,9 @@ class BusinessFactsTests(unittest.TestCase):
self.assertIn("order_staff", table_names)
self.assertIn("order_deliveries", table_names)
self.assertIn("COUNT(DISTINCT order_no)", schema["metric_guidance"]["order_count"])
self.assertIn("internal company transfer", schema["metric_guidance"]["sales_amount"])
self.assertIn("internal_parties", templates["sales_amount"]["optional_parameters"])
self.assertIn("include_internal_orders", templates["sales_amount"]["optional_parameters"])
self.assertEqual(templates["customer_count"]["required_parameters"], [])
self.assertEqual(templates["product_count"]["required_parameters"], [])
self.assertEqual(templates["customer_summary"]["required_parameters"], ["customer"])
@@ -644,6 +648,108 @@ class BusinessFactsTests(unittest.TestCase):
[("B", 1), ("C", 1)],
)
def test_business_templates_exclude_configured_internal_company_transfer_orders(self):
sync_kingdee_orders(
self.vault,
[
{
"id": "1001",
"billno": "EXT-BJD-001",
"bizdate": "2026-06-03",
"customer_number": "C01",
"customer_name": "外部客户 A",
"totalallamount": "100.00",
"org_name": "深圳市北极大电子科技有限公司",
"billentry": [
{
"id": "L1",
"material_masterid_number": "P01",
"material_masterid_name": "Part 1",
"qty": "2",
"amountandtax": "100.00",
"orderdeliverentry": [{"id": "D1", "d_plandate": "2026-06-10", "d_planqty": "2"}],
}
],
},
{
"id": "1002",
"billno": "INT-JK-BJD-001",
"bizdate": "2026-06-04",
"customer_number": "NB000001",
"customer_name": "深圳市北极大电子科技有限公司",
"totalallamount": "80.00",
"org_name": "东莞市捷康电子科技有限公司",
"billentry": [
{
"id": "L1",
"material_masterid_number": "P01",
"material_masterid_name": "Part 1",
"qty": "4",
"amountandtax": "80.00",
"orderdeliverentry": [{"id": "D2", "d_plandate": "2026-06-11", "d_planqty": "4"}],
}
],
},
{
"id": "1003",
"billno": "EXT-JK-001",
"bizdate": "2026-06-05",
"customer_number": "C02",
"customer_name": "外部客户 B",
"totalallamount": "40.00",
"org_name": "东莞市捷康电子科技有限公司",
"billentry": [
{
"id": "L1",
"material_masterid_number": "P02",
"material_masterid_name": "Part 2",
"qty": "1",
"amountandtax": "40.00",
"orderdeliverentry": [{"id": "D3", "d_plandate": "2026-06-12", "d_planqty": "1"}],
}
],
},
{
"id": "1004",
"billno": "INT-ZC-JK-001",
"bizdate": "2026-06-06",
"customer_number": "NB000002",
"customer_name": "东莞市捷康电子科技有限公司",
"totalallamount": "20.00",
"org_name": "东莞市众创机电有限公司",
"billentry": [
{
"id": "L1",
"material_masterid_number": "P03",
"material_masterid_name": "Part 3",
"qty": "5",
"amountandtax": "20.00",
"orderdeliverentry": [{"id": "D4", "d_plandate": "2026-06-13", "d_planqty": "5"}],
}
],
},
],
synced_at="2026-07-06T09:00:00Z",
)
dedupe_params = {"year": 2026, "month": 6, "internal_parties": ["北极大", "捷康", "众创"]}
count = execute_business_template(self.db_path, "order_count", **dedupe_params)
sales = execute_business_template(self.db_path, "sales_amount", **dedupe_params)
quantity = execute_business_template(self.db_path, "quantity_sum", **dedupe_params)
delivery_qty = execute_business_template(self.db_path, "delivery_plan_quantity", **dedupe_params)
included = execute_business_template(
self.db_path,
"sales_amount",
**dedupe_params,
include_internal_orders=True,
)
self.assertEqual(count["data"]["order_count"], 2)
self.assertEqual(sales["data"]["sales_amount"], 140.0)
self.assertEqual(quantity["data"]["quantity_sum"], 3.0)
self.assertEqual(delivery_qty["data"]["delivery_plan_quantity"], 3.0)
self.assertEqual(included["data"]["sales_amount"], 240.0)
def test_all_business_templates_execute_with_fixture_data(self):
self.seed_template_fixture()
required_values = {
@@ -725,6 +831,94 @@ class BusinessFactsTests(unittest.TestCase):
self.assertIn("order_count", {template["name"] for template in json.loads(schema_output.getvalue())["templates"]})
self.assertEqual(json.loads(template_output.getvalue())["data"]["order_count"], 1)
def test_cli_business_template_uses_internal_party_environment_configuration(self):
sync_kingdee_orders(
self.vault,
[
{
"id": "1001",
"billno": "EXT-BJD-001",
"bizdate": "2026-06-03",
"customer_name": "外部客户 A",
"totalallamount": "100.00",
"org_name": "深圳市北极大电子科技有限公司",
},
{
"id": "1002",
"billno": "INT-JK-BJD-001",
"bizdate": "2026-06-04",
"customer_name": "深圳市北极大电子科技有限公司",
"totalallamount": "80.00",
"org_name": "东莞市捷康电子科技有限公司",
},
],
synced_at="2026-07-06T09:00:00Z",
)
from obsidian_wiki.cli import main
template_output = StringIO()
with patch.dict(os.environ, {"OBSIDIAN_BUSINESS_INTERNAL_PARTIES": "北极大,捷康"}):
with redirect_stdout(template_output):
template_code = main(
[
"business",
"template",
"sales_amount",
"--vault",
str(self.vault),
"--year",
"2026",
"--month",
"6",
"--json",
]
)
self.assertEqual(template_code, 0)
self.assertEqual(json.loads(template_output.getvalue())["data"]["sales_amount"], 100.0)
def test_cli_business_query_uses_internal_party_environment_configuration(self):
sync_kingdee_orders(
self.vault,
[
{
"id": "1001",
"billno": "EXT-BJD-001",
"bizdate": "2026-06-03",
"customer_name": "外部客户 A",
"totalallamount": "100.00",
"org_name": "深圳市北极大电子科技有限公司",
},
{
"id": "1002",
"billno": "INT-JK-BJD-001",
"bizdate": "2026-06-04",
"customer_name": "深圳市北极大电子科技有限公司",
"totalallamount": "80.00",
"org_name": "东莞市捷康电子科技有限公司",
},
],
synced_at="2026-07-06T09:00:00Z",
)
from obsidian_wiki.cli import main
query_output = StringIO()
with patch.dict(os.environ, {"OBSIDIAN_BUSINESS_INTERNAL_PARTIES": "北极大,捷康"}):
with redirect_stdout(query_output):
query_code = main(
[
"business",
"query",
"2026-06 sales amount",
"--vault",
str(self.vault),
"--json",
]
)
self.assertEqual(query_code, 0)
self.assertEqual(json.loads(query_output.getvalue())["data"]["sales_amount"], 100.0)
def test_incremental_start_modifytime_uses_cursor_with_lookback_or_initial_days(self):
initial = compute_incremental_start_modifytime(
self.db_path,