更新各项功能
This commit is contained in:
@@ -6,15 +6,18 @@
|
||||
* - 软件部分 = Σ 产品类别为「软件(许可)」的小计
|
||||
* - 其他部分 = Σ 服务 / 开发 / 其他类小计
|
||||
* - 合同总金额 = 硬件 + 软件 + 其他
|
||||
* - 产品总成本 = Σ (数量 × 成本单价)
|
||||
* - 产品总成本 = Σ (数量 × 成本单价),软件开发行按其「开发总成本」计入
|
||||
* - 合同总利润 = 合同总金额 - 产品总成本
|
||||
*
|
||||
* 软件开发(开发类)行口径:模块报价中「开发总成本」(默认人天×统一单价,可手工改写)
|
||||
* 作为该行的成本单价并计入总成本;对外金额取用户填写的销售单价,两者差额即为毛利。
|
||||
* 归类口径:分类代码 1/2 或名称含「硬件」/「软件」分别计入硬件/软件,其余计入其他(与后端一致)。
|
||||
*/
|
||||
|
||||
const round2 = (n) => Math.round((Number(n) || 0) * 100) / 100;
|
||||
|
||||
/**
|
||||
* 软件开发模块树节点售价合计:
|
||||
* 软件开发模块树按人天的金额汇总(即「开发总成本」的默认口径):
|
||||
* - 含子节点 → 汇总子节点;
|
||||
* - 叶子节点 → 人天 × 统一人天单价。
|
||||
*/
|
||||
@@ -31,15 +34,53 @@ export function treeSellTotal(nodes, unitPrice = 0) {
|
||||
);
|
||||
}
|
||||
|
||||
/** 单行金额:成品 = 数量×单价;软件开发 = 模块树售价合计(人天×统一人天单价) */
|
||||
/**
|
||||
* 软件开发模块树叶子节点人天合计(父级不重复计,仅用于展示)。
|
||||
*/
|
||||
export function treeManDays(nodes) {
|
||||
const list = Array.isArray(nodes) ? nodes : [];
|
||||
const sum = list.reduce((acc, n) => {
|
||||
if (Array.isArray(n.children) && n.children.length) return acc + treeManDays(n.children);
|
||||
return acc + (Number(n.man_days) || 0);
|
||||
}, 0);
|
||||
return Math.round(sum * 10) / 10;
|
||||
}
|
||||
|
||||
/** 单行数量:软件开发行固定按 1 个单位计(历史数据可能缺数量,兜底为 1) */
|
||||
function lineQty(row) {
|
||||
if (row?.line_type === "dev") return Number(row?.quantity) || 1;
|
||||
return Number(row?.quantity) || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 软件开发行的开发总成本(= 模块报价带出的总价):
|
||||
* 优先取 dev_total_price(可手工改写),回退人天汇总,兼容历史数据。
|
||||
*/
|
||||
export function devCost(row) {
|
||||
const manual = Number(row?.dev_total_price) || 0;
|
||||
if (manual > 0) return round2(manual);
|
||||
return treeSellTotal(row?.dev_tree, row?.dev_unit_price);
|
||||
}
|
||||
|
||||
/**
|
||||
* 软件开发行的销售单价:由用户自行填写;
|
||||
* 未填写时依次回退「开发总成本 → 人天汇总」,避免历史/未填数据显示为 0。
|
||||
*/
|
||||
export function devSellPrice(row) {
|
||||
const p = Number(row?.price) || 0;
|
||||
if (p > 0) return round2(p);
|
||||
return devCost(row);
|
||||
}
|
||||
|
||||
/** 单行金额:成品 = 数量×单价;软件开发 = 数量×销售单价(销售单价默认由开发总成本带出,可自行填写) */
|
||||
export function lineAmount(row) {
|
||||
if (row?.line_type === "dev") return treeSellTotal(row.dev_tree, row.dev_unit_price);
|
||||
if (row?.line_type === "dev") return round2(lineQty(row) * devSellPrice(row));
|
||||
return round2((Number(row?.quantity) || 0) * (Number(row?.price) || 0));
|
||||
}
|
||||
|
||||
/** 单行成本:成品 = 数量×成本单价;软件开发不计成本 */
|
||||
/** 单行成本:成品 = 数量×成本单价;软件开发 = 数量×开发总成本(模块报价带出) */
|
||||
export function lineCost(row) {
|
||||
if (row?.line_type === "dev") return 0;
|
||||
if (row?.line_type === "dev") return round2(lineQty(row) * devCost(row));
|
||||
return round2((Number(row?.quantity) || 0) * (Number(row?.cost_price) || 0));
|
||||
}
|
||||
|
||||
@@ -51,8 +92,8 @@ export function buildSummary(products) {
|
||||
let totalCost = 0;
|
||||
rows.forEach((row) => {
|
||||
if (row?.line_type === "dev") {
|
||||
const amount = treeSellTotal(row.dev_tree, row.dev_unit_price);
|
||||
software += amount; // 软件开发计入软件部分,不计成本
|
||||
software += lineAmount(row); // 软件开发计入软件部分,并按开发总成本计入产品成本
|
||||
totalCost += lineCost(row);
|
||||
return;
|
||||
}
|
||||
const qty = Number(row?.quantity) || 0;
|
||||
|
||||
Reference in New Issue
Block a user