update bill

This commit is contained in:
ping 2026-06-01 17:45:17 +08:00
parent cf47ee13db
commit 38f849d346
3 changed files with 94 additions and 113 deletions

18
b/.mcp.json Normal file
View File

@ -0,0 +1,18 @@
{
"mcpServers": {
"mysql": {
"command": "npx",
"args": ["-y", "mcp-mysql-multi-db"],
"env": { // <-- env
"MYSQL_HOST": "localhost",
"MYSQL_PORT": "3306",
"MYSQL_USER": "root",
"MYSQL_PASSWORD": "lima2018",
"MYSQL_DATABASE": "kprod",
// 👇 INSERT/UPDATE
// "ALLOW_DML": "true",
// "ALLOW_DDL": "true"
}
}
}
}

View File

@ -75,7 +75,6 @@ SALEMODE_LABEL = {
} }
INCOME_SUBJECTS = ('折扣收入', '底价收入') INCOME_SUBJECTS = ('折扣收入', '底价收入')
PARENT_SETTLE_SUBJECT = '分销商存放资金'
SUPPLIER_SETTLE_PREFIX = '待结转' SUPPLIER_SETTLE_PREFIX = '待结转'
_SALEMODE_SQL_OWN = """ _SALEMODE_SQL_OWN = """
@ -194,9 +193,12 @@ async def _org_name(sor, orgid):
async def _is_business_owner(sor, orgid): async def _is_business_owner(sor, orgid):
# 业主机构 parentid 为空串(非 NULLorg_type='0';两者都视为顶级机构。
rows = await sor.sqlExe( rows = await sor.sqlExe(
"SELECT id FROM organization WHERE id=${id}$ AND parentid IS NULL AND del_flg='0'", """SELECT id FROM organization
{'id': orgid}, WHERE id=${id}$ AND del_flg='0'
AND ((parentid IS NULL OR parentid='') OR org_type=${owner_type}$)""",
{'id': orgid, 'owner_type': OWNER_OGR},
) )
return len(rows) > 0 return len(rows) > 0
@ -324,8 +326,12 @@ async def _protocol_snapshot(sor, accounting_orgid, customerid, providerid, prod
} }
def _estimate_finance(catalog_amount, protocol, is_owner): def _estimate_finance(catalog_amount, protocol):
"""未记账:估算本级利润与本级应付(不含代付费单独字段)。""" """未记账:估算本级利润与本级应付(本级成本)。
与已记账口径一致,本级应付取「本级作为采购方(bid)的协议」即 own_*
对业主机构与分销商统一own_discount 即上级给本级的折扣/底价)。
"""
catalog_amount = float(catalog_amount or 0) catalog_amount = float(catalog_amount or 0)
qty = protocol.get('quantity') or 1 qty = protocol.get('quantity') or 1
profit = None profit = None
@ -335,29 +341,20 @@ def _estimate_finance(catalog_amount, protocol, is_owner):
cust_mode = protocol.get('customer_salemode') cust_mode = protocol.get('customer_salemode')
own_disc = protocol.get('own_discount') own_disc = protocol.get('own_discount')
cust_disc = protocol.get('customer_discount') cust_disc = protocol.get('customer_discount')
reseller_disc = protocol.get('reseller_discount')
if own_mode == '0' or cust_mode == '0': if own_mode == '0' or cust_mode == '0':
if own_disc is not None and cust_disc is not None: if own_disc is not None and cust_disc is not None:
profit = catalog_amount * (float(cust_disc) - float(own_disc)) profit = catalog_amount * (float(cust_disc) - float(own_disc))
if is_owner: if own_disc is not None:
if own_disc is not None: settle_upstream = catalog_amount * float(own_disc)
settle_upstream = catalog_amount * float(own_disc)
elif reseller_disc is not None:
settle_upstream = catalog_amount * float(reseller_disc)
elif own_mode == '2' or cust_mode == '2': elif own_mode == '2' or cust_mode == '2':
own_price = protocol.get('own_floor_unit_price') own_price = protocol.get('own_floor_unit_price')
cust_price = protocol.get('customer_floor_unit_price') cust_price = protocol.get('customer_floor_unit_price')
reseller_price = protocol.get('reseller_floor_unit_price')
if own_price is not None and cust_price is not None: if own_price is not None and cust_price is not None:
profit = (float(cust_price) - float(own_price)) * qty profit = (float(cust_price) - float(own_price)) * qty
if is_owner and own_price is not None: if own_price is not None:
settle_upstream = float(own_price) * qty settle_upstream = float(own_price) * qty
elif not is_owner and reseller_price is not None:
settle_upstream = float(reseller_price) * qty
elif not is_owner and cust_price is not None:
settle_upstream = float(cust_price) * qty
return { return {
'profit_amount': _round_money(profit), 'profit_amount': _round_money(profit),
@ -379,8 +376,13 @@ async def _bill_detail_rows(sor, billid):
)) ))
async def _finance_from_bill_detail(sor, billid, accounting_orgid, is_owner, parent_orgid): async def _finance_from_bill_detail(sor, billid, accounting_orgid):
"""已记账:从 bill_detail 取本级利润与本级应付。""" """已记账:从 bill_detail 取本级利润与本级应付。
统一口径(业主机构与分销商一致):
- profit_amount = 本级账本「折扣收入/底价收入」贷方合计
- settle_upstream = 本级账本「待结转*销售收入」贷方合计(本级成本/本级应付上级)
"""
rows = await _bill_detail_rows(sor, billid) rows = await _bill_detail_rows(sor, billid)
profit = 0.0 profit = 0.0
settle_upstream = 0.0 settle_upstream = 0.0
@ -400,24 +402,10 @@ async def _finance_from_bill_detail(sor, billid, accounting_orgid, is_owner, par
subj = r['subjectname'] or '' subj = r['subjectname'] or ''
direction = r['accounting_dir'] direction = r['accounting_dir']
if book == accounting_orgid and subj in INCOME_SUBJECTS and direction == '贷': if book == accounting_orgid and direction == '贷':
profit += amt if subj in INCOME_SUBJECTS:
profit += amt
if is_owner: elif subj.startswith(SUPPLIER_SETTLE_PREFIX):
if (
book == accounting_orgid
and subj.startswith(SUPPLIER_SETTLE_PREFIX)
and direction == '贷'
):
settle_upstream += amt
else:
if (
parent_orgid
and book == parent_orgid
and subj == PARENT_SETTLE_SUBJECT
and direction == '借'
and r.get('participantid') == accounting_orgid
):
settle_upstream += amt settle_upstream += amt
return { return {
@ -442,16 +430,13 @@ async def _build_report_row(sor, row, accounting_orgid, is_owner):
protocol = await _protocol_snapshot( protocol = await _protocol_snapshot(
sor, accounting_orgid, customerid, providerid, productid, bill_date, quantity, sor, accounting_orgid, customerid, providerid, productid, bill_date, quantity,
) )
parent_orgid = protocol['parent_orgid']
settle_meta = await _settle_upstream_meta(sor, accounting_orgid, providerid) settle_meta = await _settle_upstream_meta(sor, accounting_orgid, providerid)
bill_state = _row_get(row, 'bill_state') bill_state = _row_get(row, 'bill_state')
if str(bill_state) == '1': if str(bill_state) == '1':
amounts = await _finance_from_bill_detail( amounts = await _finance_from_bill_detail(sor, bill_id, accounting_orgid)
sor, bill_id, accounting_orgid, is_owner, parent_orgid,
)
else: else:
amounts = _estimate_finance(catalog_amount, protocol, is_owner) amounts = _estimate_finance(catalog_amount, protocol)
amounts['bill_detail_legs'] = [] amounts['bill_detail_legs'] = []
product_name = _row_get(row, 'product_name') product_name = _row_get(row, 'product_name')
@ -652,7 +637,7 @@ def _customer_segment(customer_parentid, accounting_orgid, reseller_id_set):
return None return None
async def _bill_finance_amounts(sor, row, accounting_orgid, is_owner, parent_orgid): async def _bill_finance_amounts(sor, row, accounting_orgid):
"""单笔:销售额、本级利润、本级应付上级/供应商。""" """单笔:销售额、本级利润、本级应付上级/供应商。"""
sales = float(_row_get(row, 'customer_pay_amount') or 0) sales = float(_row_get(row, 'customer_pay_amount') or 0)
bill_id = _row_get(row, 'bill_id') bill_id = _row_get(row, 'bill_id')
@ -660,9 +645,7 @@ async def _bill_finance_amounts(sor, row, accounting_orgid, is_owner, parent_org
quantity = int(_row_get(row, 'quantity') or 1) quantity = int(_row_get(row, 'quantity') or 1)
if str(bill_state) == '1': if str(bill_state) == '1':
fin = await _finance_from_bill_detail( fin = await _finance_from_bill_detail(sor, bill_id, accounting_orgid)
sor, bill_id, accounting_orgid, is_owner, parent_orgid,
)
else: else:
catalog = float(_row_get(row, 'catalog_amount') or 0) catalog = float(_row_get(row, 'catalog_amount') or 0)
protocol = await _protocol_snapshot( protocol = await _protocol_snapshot(
@ -674,7 +657,7 @@ async def _bill_finance_amounts(sor, row, accounting_orgid, is_owner, parent_org
_row_get(row, 'bill_date'), _row_get(row, 'bill_date'),
quantity, quantity,
) )
fin = _estimate_finance(catalog, protocol, is_owner) fin = _estimate_finance(catalog, protocol)
profit = float(fin.get('profit_amount') or 0) profit = float(fin.get('profit_amount') or 0)
settle = float(fin.get('settle_upstream_amount') or 0) settle = float(fin.get('settle_upstream_amount') or 0)
@ -878,9 +861,9 @@ async def finance_order_report(ns=None):
userid, start_date, end_date, customerid, productid, order_id, bill_state userid, start_date, end_date, customerid, productid, order_id, bill_state
current_page (默认1), page_size (默认20, 最大100) current_page (默认1), page_size (默认20, 最大100)
返回 finance.settle_upstream_* 返回 finance.settle_upstream_*(统一口径,取本级账本「待结转*销售收入」贷方)
- 本机构:应付供应商(待结转* 贷方,仅本机构账本) - 本机构:应付供应商
- 分销商:应付直接上级上级账本「分销商存放资金」借方participant=本级 - 分销商:应付直接上级机构(金额=本级成本,与本级账本待结转一致
""" """
ns = ns or {} ns = ns or {}
accounting_orgid = ns.get('accounting_orgid') accounting_orgid = ns.get('accounting_orgid')
@ -1053,7 +1036,7 @@ async def finance_billing_overview(ns=None):
口径(与订单明细接口一致): 口径(与订单明细接口一致):
sales_total = 客户实付合计 bill.amount sales_total = 客户实付合计 bill.amount
profit_total = 本级账本折扣收入+底价收入bill_detail或协议估算 profit_total = 本级账本折扣收入+底价收入bill_detail或协议估算
settle_upstream_total= 本机构→供应商待结转;分销→上级账本分销商存放资金借方 settle_upstream_total= 本级账本「待结转*销售收入」贷方合计(本级成本/应付上级,业主与分销统一)
分段: 分段:
direct_customers 直属客户cust.parentid = accounting_orgid direct_customers 直属客户cust.parentid = accounting_orgid
@ -1084,7 +1067,6 @@ async def finance_billing_overview(ns=None):
sor, accounting_orgid, include_sub, sor, accounting_orgid, include_sub,
) )
is_owner = await _is_business_owner(sor, accounting_orgid) is_owner = await _is_business_owner(sor, accounting_orgid)
parent_orgid = await get_parent_orgid(sor, accounting_orgid)
conditions = ["b.del_flg = '0'", "o.del_flg = '0'", scope_sql] conditions = ["b.del_flg = '0'", "o.del_flg = '0'", scope_sql]
params = dict(scope_params) params = dict(scope_params)
@ -1128,7 +1110,7 @@ async def finance_billing_overview(ns=None):
continue continue
try: try:
sales, profit, settle, _src = await _bill_finance_amounts( sales, profit, settle, _src = await _bill_finance_amounts(
sor, row, accounting_orgid, is_owner, parent_orgid, sor, row, accounting_orgid,
) )
except Exception as exc: except Exception as exc:
errors.append({ errors.append({
@ -1230,11 +1212,10 @@ async def finance_billing_overview(ns=None):
# _report = params_kw.get('report') or params_kw.get('api') or 'order_list' # _report = params_kw.get('report') or params_kw.get('api') or 'order_list'
_report = None _report = None
if _report in ('overview', 'billing_overview', 'finance_billing_overview'): # if _report in ('overview', 'billing_overview', 'finance_billing_overview'):
ret = await finance_billing_overview(params_kw) # ret = await finance_billing_overview(params_kw)
elif _report in ('detail', 'order_detail'): # elif _report in ('detail', 'order_detail'):
ret = await finance_order_report_detail(params_kw) # ret = await finance_order_report_detail(params_kw)
else: # else:
ret = await finance_order_report(params_kw) ret = await finance_order_report(params_kw)
return ret return ret
return ret

View File

@ -75,7 +75,6 @@ SALEMODE_LABEL = {
} }
INCOME_SUBJECTS = ('折扣收入', '底价收入') INCOME_SUBJECTS = ('折扣收入', '底价收入')
PARENT_SETTLE_SUBJECT = '分销商存放资金'
SUPPLIER_SETTLE_PREFIX = '待结转' SUPPLIER_SETTLE_PREFIX = '待结转'
_SALEMODE_SQL_OWN = """ _SALEMODE_SQL_OWN = """
@ -194,9 +193,12 @@ async def _org_name(sor, orgid):
async def _is_business_owner(sor, orgid): async def _is_business_owner(sor, orgid):
# 业主机构 parentid 为空串(非 NULLorg_type='0';两者都视为顶级机构。
rows = await sor.sqlExe( rows = await sor.sqlExe(
"SELECT id FROM organization WHERE id=${id}$ AND parentid IS NULL AND del_flg='0'", """SELECT id FROM organization
{'id': orgid}, WHERE id=${id}$ AND del_flg='0'
AND ((parentid IS NULL OR parentid='') OR org_type=${owner_type}$)""",
{'id': orgid, 'owner_type': OWNER_OGR},
) )
return len(rows) > 0 return len(rows) > 0
@ -324,8 +326,12 @@ async def _protocol_snapshot(sor, accounting_orgid, customerid, providerid, prod
} }
def _estimate_finance(catalog_amount, protocol, is_owner): def _estimate_finance(catalog_amount, protocol):
"""未记账:估算本级利润与本级应付(不含代付费单独字段)。""" """未记账:估算本级利润与本级应付(本级成本)。
与已记账口径一致,本级应付取「本级作为采购方(bid)的协议」即 own_*
对业主机构与分销商统一own_discount 即上级给本级的折扣/底价)。
"""
catalog_amount = float(catalog_amount or 0) catalog_amount = float(catalog_amount or 0)
qty = protocol.get('quantity') or 1 qty = protocol.get('quantity') or 1
profit = None profit = None
@ -335,29 +341,20 @@ def _estimate_finance(catalog_amount, protocol, is_owner):
cust_mode = protocol.get('customer_salemode') cust_mode = protocol.get('customer_salemode')
own_disc = protocol.get('own_discount') own_disc = protocol.get('own_discount')
cust_disc = protocol.get('customer_discount') cust_disc = protocol.get('customer_discount')
reseller_disc = protocol.get('reseller_discount')
if own_mode == '0' or cust_mode == '0': if own_mode == '0' or cust_mode == '0':
if own_disc is not None and cust_disc is not None: if own_disc is not None and cust_disc is not None:
profit = catalog_amount * (float(cust_disc) - float(own_disc)) profit = catalog_amount * (float(cust_disc) - float(own_disc))
if is_owner: if own_disc is not None:
if own_disc is not None: settle_upstream = catalog_amount * float(own_disc)
settle_upstream = catalog_amount * float(own_disc)
elif reseller_disc is not None:
settle_upstream = catalog_amount * float(reseller_disc)
elif own_mode == '2' or cust_mode == '2': elif own_mode == '2' or cust_mode == '2':
own_price = protocol.get('own_floor_unit_price') own_price = protocol.get('own_floor_unit_price')
cust_price = protocol.get('customer_floor_unit_price') cust_price = protocol.get('customer_floor_unit_price')
reseller_price = protocol.get('reseller_floor_unit_price')
if own_price is not None and cust_price is not None: if own_price is not None and cust_price is not None:
profit = (float(cust_price) - float(own_price)) * qty profit = (float(cust_price) - float(own_price)) * qty
if is_owner and own_price is not None: if own_price is not None:
settle_upstream = float(own_price) * qty settle_upstream = float(own_price) * qty
elif not is_owner and reseller_price is not None:
settle_upstream = float(reseller_price) * qty
elif not is_owner and cust_price is not None:
settle_upstream = float(cust_price) * qty
return { return {
'profit_amount': _round_money(profit), 'profit_amount': _round_money(profit),
@ -379,8 +376,13 @@ async def _bill_detail_rows(sor, billid):
)) ))
async def _finance_from_bill_detail(sor, billid, accounting_orgid, is_owner, parent_orgid): async def _finance_from_bill_detail(sor, billid, accounting_orgid):
"""已记账:从 bill_detail 取本级利润与本级应付。""" """已记账:从 bill_detail 取本级利润与本级应付。
统一口径(业主机构与分销商一致):
- profit_amount = 本级账本「折扣收入/底价收入」贷方合计
- settle_upstream = 本级账本「待结转*销售收入」贷方合计(本级成本/本级应付上级)
"""
rows = await _bill_detail_rows(sor, billid) rows = await _bill_detail_rows(sor, billid)
profit = 0.0 profit = 0.0
settle_upstream = 0.0 settle_upstream = 0.0
@ -400,24 +402,10 @@ async def _finance_from_bill_detail(sor, billid, accounting_orgid, is_owner, par
subj = r['subjectname'] or '' subj = r['subjectname'] or ''
direction = r['accounting_dir'] direction = r['accounting_dir']
if book == accounting_orgid and subj in INCOME_SUBJECTS and direction == '贷': if book == accounting_orgid and direction == '贷':
profit += amt if subj in INCOME_SUBJECTS:
profit += amt
if is_owner: elif subj.startswith(SUPPLIER_SETTLE_PREFIX):
if (
book == accounting_orgid
and subj.startswith(SUPPLIER_SETTLE_PREFIX)
and direction == '贷'
):
settle_upstream += amt
else:
if (
parent_orgid
and book == parent_orgid
and subj == PARENT_SETTLE_SUBJECT
and direction == '借'
and r.get('participantid') == accounting_orgid
):
settle_upstream += amt settle_upstream += amt
return { return {
@ -442,16 +430,13 @@ async def _build_report_row(sor, row, accounting_orgid, is_owner):
protocol = await _protocol_snapshot( protocol = await _protocol_snapshot(
sor, accounting_orgid, customerid, providerid, productid, bill_date, quantity, sor, accounting_orgid, customerid, providerid, productid, bill_date, quantity,
) )
parent_orgid = protocol['parent_orgid']
settle_meta = await _settle_upstream_meta(sor, accounting_orgid, providerid) settle_meta = await _settle_upstream_meta(sor, accounting_orgid, providerid)
bill_state = _row_get(row, 'bill_state') bill_state = _row_get(row, 'bill_state')
if str(bill_state) == '1': if str(bill_state) == '1':
amounts = await _finance_from_bill_detail( amounts = await _finance_from_bill_detail(sor, bill_id, accounting_orgid)
sor, bill_id, accounting_orgid, is_owner, parent_orgid,
)
else: else:
amounts = _estimate_finance(catalog_amount, protocol, is_owner) amounts = _estimate_finance(catalog_amount, protocol)
amounts['bill_detail_legs'] = [] amounts['bill_detail_legs'] = []
product_name = _row_get(row, 'product_name') product_name = _row_get(row, 'product_name')
@ -652,7 +637,7 @@ def _customer_segment(customer_parentid, accounting_orgid, reseller_id_set):
return None return None
async def _bill_finance_amounts(sor, row, accounting_orgid, is_owner, parent_orgid): async def _bill_finance_amounts(sor, row, accounting_orgid):
"""单笔:销售额、本级利润、本级应付上级/供应商。""" """单笔:销售额、本级利润、本级应付上级/供应商。"""
sales = float(_row_get(row, 'customer_pay_amount') or 0) sales = float(_row_get(row, 'customer_pay_amount') or 0)
bill_id = _row_get(row, 'bill_id') bill_id = _row_get(row, 'bill_id')
@ -660,9 +645,7 @@ async def _bill_finance_amounts(sor, row, accounting_orgid, is_owner, parent_org
quantity = int(_row_get(row, 'quantity') or 1) quantity = int(_row_get(row, 'quantity') or 1)
if str(bill_state) == '1': if str(bill_state) == '1':
fin = await _finance_from_bill_detail( fin = await _finance_from_bill_detail(sor, bill_id, accounting_orgid)
sor, bill_id, accounting_orgid, is_owner, parent_orgid,
)
else: else:
catalog = float(_row_get(row, 'catalog_amount') or 0) catalog = float(_row_get(row, 'catalog_amount') or 0)
protocol = await _protocol_snapshot( protocol = await _protocol_snapshot(
@ -674,7 +657,7 @@ async def _bill_finance_amounts(sor, row, accounting_orgid, is_owner, parent_org
_row_get(row, 'bill_date'), _row_get(row, 'bill_date'),
quantity, quantity,
) )
fin = _estimate_finance(catalog, protocol, is_owner) fin = _estimate_finance(catalog, protocol)
profit = float(fin.get('profit_amount') or 0) profit = float(fin.get('profit_amount') or 0)
settle = float(fin.get('settle_upstream_amount') or 0) settle = float(fin.get('settle_upstream_amount') or 0)
@ -878,9 +861,9 @@ async def finance_order_report(ns=None):
userid, start_date, end_date, customerid, productid, order_id, bill_state userid, start_date, end_date, customerid, productid, order_id, bill_state
current_page (默认1), page_size (默认20, 最大100) current_page (默认1), page_size (默认20, 最大100)
返回 finance.settle_upstream_* 返回 finance.settle_upstream_*(统一口径,取本级账本「待结转*销售收入」贷方)
- 本机构:应付供应商(待结转* 贷方,仅本机构账本) - 本机构:应付供应商
- 分销商:应付直接上级上级账本「分销商存放资金」借方participant=本级 - 分销商:应付直接上级机构(金额=本级成本,与本级账本待结转一致
""" """
ns = ns or {} ns = ns or {}
accounting_orgid = ns.get('accounting_orgid') accounting_orgid = ns.get('accounting_orgid')
@ -1053,7 +1036,7 @@ async def finance_billing_overview(ns=None):
口径(与订单明细接口一致): 口径(与订单明细接口一致):
sales_total = 客户实付合计 bill.amount sales_total = 客户实付合计 bill.amount
profit_total = 本级账本折扣收入+底价收入bill_detail或协议估算 profit_total = 本级账本折扣收入+底价收入bill_detail或协议估算
settle_upstream_total= 本机构→供应商待结转;分销→上级账本分销商存放资金借方 settle_upstream_total= 本级账本「待结转*销售收入」贷方合计(本级成本/应付上级,业主与分销统一)
分段: 分段:
direct_customers 直属客户cust.parentid = accounting_orgid direct_customers 直属客户cust.parentid = accounting_orgid
@ -1084,7 +1067,6 @@ async def finance_billing_overview(ns=None):
sor, accounting_orgid, include_sub, sor, accounting_orgid, include_sub,
) )
is_owner = await _is_business_owner(sor, accounting_orgid) is_owner = await _is_business_owner(sor, accounting_orgid)
parent_orgid = await get_parent_orgid(sor, accounting_orgid)
conditions = ["b.del_flg = '0'", "o.del_flg = '0'", scope_sql] conditions = ["b.del_flg = '0'", "o.del_flg = '0'", scope_sql]
params = dict(scope_params) params = dict(scope_params)
@ -1128,7 +1110,7 @@ async def finance_billing_overview(ns=None):
continue continue
try: try:
sales, profit, settle, _src = await _bill_finance_amounts( sales, profit, settle, _src = await _bill_finance_amounts(
sor, row, accounting_orgid, is_owner, parent_orgid, sor, row, accounting_orgid,
) )
except Exception as exc: except Exception as exc:
errors.append({ errors.append({