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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user