diff --git a/b/account_config.sql b/b/account_config.sql new file mode 100644 index 0000000..634bb14 --- /dev/null +++ b/b/account_config.sql @@ -0,0 +1,60 @@ +/* + Navicat Premium Data Transfer + + Source Server : kbossdev + Source Server Type : MariaDB + Source Server Version : 100622 + Source Host : db:3306 + Source Schema : kboss_dev + + Target Server Type : MariaDB + Target Server Version : 100622 + File Encoding : 65001 + + Date: 18/06/2026 17:11:12 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for account_config +-- ---------------------------- +DROP TABLE IF EXISTS `account_config`; +CREATE TABLE `account_config` ( + `id` varchar(32) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT 'id', + `partytype` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '参与方类型', + `subjectname` varchar(32) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '科目名称', + `del_flg` varchar(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '0' COMMENT '删除标志', + `create_at` timestamp NOT NULL DEFAULT current_timestamp() COMMENT '创建时间戳', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '账户配置表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of account_config +-- ---------------------------- +INSERT INTO `account_config` VALUES ('acc001', '本机构', '资金账号', '0', '2023-08-03 17:40:34'); +INSERT INTO `account_config` VALUES ('acc002', '本机构', '折扣收入', '0', '2023-08-03 17:40:34'); +INSERT INTO `account_config` VALUES ('acc003', '本机构', '底价收入', '0', '2023-08-03 17:40:34'); +INSERT INTO `account_config` VALUES ('acc004', '本机构', '客户折扣支出', '0', '2023-08-03 17:40:34'); +INSERT INTO `account_config` VALUES ('acc005', '本机构', '存放供应商资金', '0', '2023-08-03 17:40:34'); +INSERT INTO `account_config` VALUES ('acc006', '本机构', '分销商代付费销售', '0', '2023-08-03 17:40:34'); +INSERT INTO `account_config` VALUES ('acc007', '本机构', '支付宝手续费', '0', '2023-08-03 17:40:34'); +INSERT INTO `account_config` VALUES ('acc008', '客户', '业务账', '0', '2023-08-03 17:40:34'); +INSERT INTO `account_config` VALUES ('acc009', '供应商', '待结转折扣销售收入', '0', '2023-08-03 17:40:34'); +INSERT INTO `account_config` VALUES ('acc010', '供应商', '待结转代付费销售收入', '0', '2023-08-03 17:40:34'); +INSERT INTO `account_config` VALUES ('acc011', '供应商', '待结转底价销售收入', '0', '2023-08-03 17:40:34'); +INSERT INTO `account_config` VALUES ('acc012', '分销商', '分销商存放资金', '0', '2023-08-03 17:40:34'); +INSERT INTO `account_config` VALUES ('acc013', '分销商', '待结转折扣支出', '0', '2023-08-03 17:40:34'); +INSERT INTO `account_config` VALUES ('acc014', '分销商', '待结转代付费支出', '0', '2023-08-03 17:40:34'); +INSERT INTO `account_config` VALUES ('acc015', '分销商', '待结转底价支出', '0', '2023-08-03 17:40:34'); +INSERT INTO `account_config` VALUES ('acc016', '分销商', '代付费销售', '0', '2023-08-03 17:40:34'); +INSERT INTO `account_config` VALUES ('acc017', '客户', '算力券', '0', '2025-03-31 11:36:11'); +INSERT INTO `account_config` VALUES ('acc019', '算力券发行方', '算力券发放', '0', '2025-04-08 11:21:33'); +INSERT INTO `account_config` VALUES ('acc020', '算力券发行方', '算力券消费', '0', '2025-04-08 11:21:43'); +INSERT INTO `account_config` VALUES ('acc021', '算力券承销方', '待结算算力券', '0', '2025-04-08 11:29:56'); +INSERT INTO `account_config` VALUES ('acc022', '本机构', '算力劵临时账户', '0', '2025-04-10 16:34:46'); +INSERT INTO `account_config` VALUES ('acc023', '分销商', '算力劵临时账户', '0', '2025-04-10 16:34:55'); +INSERT INTO `account_config` VALUES ('acc024', '供应商', '算力劵临时账户', '0', '2025-04-10 16:35:03'); + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/b/accounting_config.sql b/b/accounting_config.sql new file mode 100644 index 0000000..3c6c575 --- /dev/null +++ b/b/accounting_config.sql @@ -0,0 +1,131 @@ +/* + Navicat Premium Data Transfer + + Source Server : kbossdev + Source Server Type : MariaDB + Source Server Version : 100622 + Source Host : db:3306 + Source Schema : kboss_dev + + Target Server Type : MariaDB + Target Server Version : 100622 + File Encoding : 65001 + + Date: 18/06/2026 17:11:43 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for accounting_config +-- ---------------------------- +DROP TABLE IF EXISTS `accounting_config`; +CREATE TABLE `accounting_config` ( + `id` varchar(32) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT 'id', + `action` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '交易', + `specstr` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '特性', + `accounting_orgtype` varchar(32) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '账务机构', + `accounting_dir` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '记账方向', + `orgtype` varchar(32) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '机构类型', + `subjectname` varchar(21) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '科目名', + `amt_pattern` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '金额模板', + `del_flg` varchar(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '0' COMMENT '删除标志', + `create_at` timestamp NOT NULL DEFAULT current_timestamp() COMMENT '创建时间戳', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '记账配置表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of accounting_config +-- ---------------------------- +INSERT INTO `accounting_config` VALUES ('accc-01', '充值', '充值', '客户所在机构', '借', '本机构', '资金账号', '${交易金额}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-02', '充值', '充值', '客户所在机构', '贷', '客户', '业务账', '${交易金额}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-03', '充值', '充值', '分销商机构', '借', '本机构', '资金账号', '${交易金额}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-04', '充值', '充值', '分销商机构', '贷', '分销商', '分销商存放资金', '${交易金额}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-05', '交易', '付费-折扣', '客户所在机构', '借', '客户', '业务账', '${交易金额}$ * ${客户折扣}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-06', '交易', '付费-折扣', '客户所在机构', '贷', '本机构', '折扣收入', '${交易金额}$ * (${客户折扣}$ - ${本方折扣}$)', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-07', '交易', '付费-折扣', '客户所在机构', '贷', '供应商', '待结转折扣销售收入', '${交易金额}$ * ${本方折扣}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-08', '交易', '付费-折扣-折扣', '分销商机构', '借', '分销商', '分销商存放资金', '${交易金额}$ * ${分销商折扣}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-09', '交易', '付费-折扣-折扣', '分销商机构', '贷', '本机构', '折扣收入', '${交易金额}$ * (${分销商折扣}$ - ${本方折扣}$)', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-10', '交易', '付费-折扣-折扣', '分销商机构', '贷', '供应商', '待结转折扣销售收入', '${交易金额}$ * ${本方折扣}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-11', '交易', '付费-折扣-代付费', '分销商机构', '借', '分销商', '分销商存放资金', '${交易金额}$ * ${客户折扣}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-12', '交易', '付费-折扣-代付费', '分销商机构', '贷', '本机构', '折扣收入', '${交易金额}$ * (${客户折扣}$ - ${本方折扣}$)', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-13', '交易', '付费-折扣-代付费', '分销商机构', '贷', '供应商', '待结转折扣销售收入', '${交易金额}$ * ${本方折扣}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-14', '交易', '付费-折扣-代付费', '分销商机构', '借', '本机构', '分销商代付费销售', '${交易金额}$ * ${客户折扣}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-15', '交易', '付费-折扣-代付费', '分销商机构', '贷', '分销商', '代付费销售', '${交易金额}$ * ${客户折扣}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-16', '交易', '付费-代付费', '客户所在机构', '借', '客户', '业务账', '${交易金额}$ * ${客户折扣}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-17', '交易', '付费-代付费', '客户所在机构', '借', '本机构', '客户折扣支出', '${交易金额}$ * (1 - ${客户折扣}$)', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-18', '交易', '付费-代付费', '客户所在机构', '贷', '供应商', '待结转代付费销售收入', '${交易金额}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-19', '交易', '付费-代付费-代付费', '分销商机构', '借', '分销商', '分销商存放资金', '${交易金额}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-20', '交易', '付费-代付费-代付费', '分销商机构', '贷', '供应商', '待结转代付费销售收入', '${交易金额}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-21', '交易', '付费-代付费-代付费', '分销商机构', '借', '本机构', '分销商代付费销售', '${交易金额}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-22', '交易', '付费-代付费-代付费', '分销商机构', '贷', '分销商', '代付费销售', '${交易金额}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-23', '交易', '付费-底价', '客户所在机构', '借', '客户', '业务账', '${客户售价}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-24', '交易', '付费-底价', '客户所在机构', '贷', '本机构', '底价收入', '${客户售价}$ - ${进价}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-25', '交易', '付费-底价', '客户所在机构', '贷', '供应商', '待结转底价销售收入', '${进价}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-26', '交易', '付费-底价-底价', '分销商机构', '借', '分销商', '分销商存放资金', '${分销商售价}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-27', '交易', '付费-底价-底价', '分销商机构', '贷', '本机构', '底价收入', '${分销商售价}$ - ${进价}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-28', '交易', '付费-底价-底价', '分销商机构', '贷', '供应商', '待结转底价销售收入', '${进价}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-29', '交易', '付费-底价-底价', '分销商机构', '借', '分销商', '分销商存放资金', '${客户售价}$ * ${分销商售价}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-30', '交易', '付费-底价-底价', '分销商机构', '贷', '本机构', '底价收入', '${客户售价}$ * ${分销商售价}$ - ${进价}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-31', '交易', '付费-底价-底价', '分销商机构', '贷', '供应商', '待结转底价销售收入', '${进价}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-32', '交易', '付费-底价-代付费', '分销商机构', '借', '分销商', '分销商存放资金', '${客户售价}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-33', '交易', '付费-底价-代付费', '分销商机构', '贷', '本机构', '底价收入', '${客户售价}$ - ${进价}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-34', '交易', '付费-底价-代付费', '分销商机构', '贷', '供应商', '待结转底价销售收入', '${进价}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-35', '交易', '付费-底价-代付费', '分销商机构', '借', '本机构', '分销商代付费销售', '${客户售价}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-36', '交易', '付费-底价-代付费', '分销商机构', '贷', '分销商', '代付费销售', '${客户售价}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-37', '结算', '结算-折扣-实时', NULL, '借', '供应商', '待结转折扣销售收入', '${贷-供应商-待结转折扣销售收入}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-38', '结算', '结算-折扣-实时', NULL, '贷', '本机构', '资金账号', '${贷-供应商-待结转折扣销售收入}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-39', '结算', '结算-代付费-实时', NULL, '借', '供应商', '待结转代付费销售收入', '${贷-供应商-待结转代付费销售收入}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-40', '结算', '结算-代付费-实时', NULL, '贷', '本机构', '资金账号', '${贷-供应商-待结转代付费销售收入}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-41', '结算', '结算-底价-实时', NULL, '借', '供应商', '待结转底价销售收入', '${贷-供应商-待结转底价销售收入}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-42', '结算', '结算-底价-实时', NULL, '贷', '本机构', '资金账号', '${贷-供应商-待结转底价销售收入}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-43', '结算', '结算-折扣', NULL, '借', '供应商', '待结转折扣销售收入', '${交易金额}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-44', '结算', '结算-折扣', NULL, '贷', '本机构', '资金账号', '${交易金额}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-45', '结算', '结算-代付费', NULL, '借', '供应商', '待结转代付费销售收入', '${交易金额}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-46', '结算', '结算-代付费', NULL, '贷', '本机构', '资金账号', '${交易金额}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-47', '结算', '结算-底价', NULL, '借', '供应商', '待结转底价销售收入', '${交易金额}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-48', '结算', '结算-底价', NULL, '贷', '本机构', '资金账号', '${交易金额}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-49', '充值', '支付宝充值', '客户所在机构', '借', '本机构', '资金账号', '${交易金额}$ - ${手续费}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-50', '充值', '支付宝充值', '客户所在机构', '借', '本机构', '支付宝手续费', '${手续费}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-51', '充值', '支付宝充值', '客户所在机构', '贷', '客户', '业务账', '${交易金额}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-52', '充值', '支付宝充值', '分销商机构', '借', '本机构', '资金账号', '${交易金额}$ - ${手续费}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-53', '充值', '支付宝充值', '分销商机构', '贷', '分销商', '分销商存放资金', '${交易金额}$ - ${手续费}$', '0', '2023-08-03 17:40:34'); +INSERT INTO `accounting_config` VALUES ('accc-54', '算力劵发行', '算力劵充值', '算力劵承销方', '借', '算力劵发行方', '算力劵发行', '${交易金额}$', '0', '2025-04-08 17:33:03'); +INSERT INTO `accounting_config` VALUES ('accc-55', '算力劵发行', '算力劵充值', '算力劵承销方', '贷', '本机构', '算力劵临时账户', '${交易金额}$', '0', '2025-04-08 17:37:44'); +INSERT INTO `accounting_config` VALUES ('accc-56', '算力劵发放', '算力劵充值', '分销商机构', '借', '本机构', '算力劵临时账户', '${交易金额}$', '0', '2025-04-08 17:37:44'); +INSERT INTO `accounting_config` VALUES ('accc-57', '算力劵发放', '算力劵充值', '分销商机构', '贷', '分销商', '算力劵临时账户', '${交易金额}$', '0', '2025-04-08 17:37:44'); +INSERT INTO `accounting_config` VALUES ('accc-58', '算力劵发放', '算力劵充值', '客户所在机构', '借', '本机构', '算力劵临时账户', '${交易金额}$', '0', '2025-04-08 17:37:44'); +INSERT INTO `accounting_config` VALUES ('accc-59', '算力劵发放', '算力劵充值', '客户所在机构', '贷', '客户', '算力劵', '${交易金额}$', '0', '2025-04-08 17:37:44'); +INSERT INTO `accounting_config` VALUES ('accc-60', '算力劵交易', '算力券-付费-折扣', '客户所在机构', '借', '客户', '算力劵', '${交易金额}*${客户折扣}$', '0', '2025-04-08 17:37:44'); +INSERT INTO `accounting_config` VALUES ('accc-61', '算力劵交易', '算力券-付费-折扣', '客户所在机构', '贷', '本机构', '算力劵临时账户', '${交易金额}*${客户折扣}$', '0', '2025-04-08 17:37:44'); +INSERT INTO `accounting_config` VALUES ('accc-62', '算力劵交易', '算力券-付费-折扣', '客户所在机构', '借', '供应商', '算力劵临时账户', '${交易金额}*${客户折扣}$', '0', '2025-04-08 17:37:44'); +INSERT INTO `accounting_config` VALUES ('accc-63', '算力劵交易', '算力券-付费-折扣', '客户所在机构', '贷', '本机构', '折扣收入', '${交易金额}*(${客户折扣}-${本方折扣})$', '0', '2025-04-08 17:37:44'); +INSERT INTO `accounting_config` VALUES ('accc-64', '算力劵交易', '算力券-付费-折扣', '客户所在机构', '贷', '供应商', '待结转折扣销售收入', '${交易金额}$*${分销商折扣}$', '0', '2025-04-08 17:37:44'); +INSERT INTO `accounting_config` VALUES ('accc-65', '算力劵交易', '算力券-付费-折扣-折扣', '分销商机构', '借', '本机构', '算力劵临时账户', '${交易金额}$*${客户折扣}$', '0', '2025-04-08 17:37:44'); +INSERT INTO `accounting_config` VALUES ('accc-66', '算力劵交易', '算力券-付费-折扣-折扣', '分销商机构', '贷', '供应商', '算力劵临时账户', '${交易金额}$*${客户折扣}$', '0', '2025-04-08 17:37:44'); +INSERT INTO `accounting_config` VALUES ('accc-67', '算力劵交易', '算力券-付费-折扣-折扣', '分销商机构', '贷', '本机构', '折扣收入', '${交易金额}$*(${分销商折扣}$ -${本方折扣}$)', '0', '2025-04-08 17:37:44'); +INSERT INTO `accounting_config` VALUES ('accc-68', '算力劵交易', '算力券-付费-折扣-折扣', '分销商机构', '贷', '供应商', '待结转折扣销售收入', '${交易金额}$*${本方折扣}$', '0', '2025-04-08 17:37:44'); +INSERT INTO `accounting_config` VALUES ('accc-69', '算力劵交易', '算力券-付费-折扣-折扣', '分销商机构', '借', '分销商', '分销商存放资金', '${交易金额}$*${分销商折扣}$', '0', '2025-04-08 17:37:44'); +INSERT INTO `accounting_config` VALUES ('accc-70', '算力劵交易', '算力券-付费-底价', '客户所在机构', '借', '客户', '算力劵', '${客户售价}$', '0', '2025-04-08 17:37:44'); +INSERT INTO `accounting_config` VALUES ('accc-71', '算力劵交易', '算力券-付费-底价', '客户所在机构', '贷', '本机构', '算力劵临时账户', '${客户售价}$ - ${进价}$', '0', '2025-04-08 17:37:44'); +INSERT INTO `accounting_config` VALUES ('accc-72', '算力劵交易', '算力券-付费-底价', '客户所在机构', '借', '供应商', '算力劵临时账户', '${客户售价}$ - ${进价}$', '0', '2025-04-08 17:37:44'); +INSERT INTO `accounting_config` VALUES ('accc-73', '算力劵交易', '算力券-付费-底价', '客户所在机构', '贷', '本机构', '底价收入', '${客户售价}$ - ${进价}$', '0', '2025-04-08 17:37:44'); +INSERT INTO `accounting_config` VALUES ('accc-74', '算力劵交易', '算力券-付费-底价', '客户所在机构', '贷', '供应商', '待结转底价销售收入', '${进价}$', '0', '2025-04-08 17:37:44'); +INSERT INTO `accounting_config` VALUES ('accc-75', '算力劵交易', '算力券-付费-底价-底价', '分销商机构', '借', '本机构', '算力劵临时账户', '${客户售价}$ - ${进价}$', '0', '2025-04-08 17:37:44'); +INSERT INTO `accounting_config` VALUES ('accc-76', '算力劵交易', '算力券-付费-底价-底价', '分销商机构', '贷', '供应商', '算力劵临时账户', '${进价}$', '0', '2025-04-08 17:37:44'); +INSERT INTO `accounting_config` VALUES ('accc-77', '算力劵交易', '算力券-付费-底价-底价', '分销商机构', '贷', '本机构', '底价收入', '${客户售价}$ - ${进价}$', '0', '2025-04-08 17:37:44'); +INSERT INTO `accounting_config` VALUES ('accc-78', '算力劵交易', '算力券-付费-底价-底价', '分销商机构', '贷', '供应商', '待结转底价销售收入', '${进价}$', '0', '2025-04-08 17:37:44'); +INSERT INTO `accounting_config` VALUES ('accc-79', '算力劵交易', '算力券-付费-底价-底价', '分销商机构', '借', '分销商', '分销商存放资金', '${客户售价}$ * ${分销商售价}$ - ${进价}$', '0', '2025-04-08 17:37:44'); +INSERT INTO `accounting_config` VALUES ('accc-80', '算力劵交易', '算力券-付费-底价', '客户所在机构', '借', '客户', '算力劵', '${交易金额}$ * ${客户折扣}$', '0', '2025-04-08 17:37:44'); +INSERT INTO `accounting_config` VALUES ('accc-81', '算力劵交易', '算力券-付费-底价', '客户所在机构', '贷', '本机构', '算力劵临时账户', '${交易金额}$ * ${客户折扣}$', '0', '2025-04-08 17:37:44'); +INSERT INTO `accounting_config` VALUES ('accc-82', '算力劵交易', '算力券-付费-代付费', '客户所在机构', '借', '供应商', '算力劵临时账户', '${交易金额}$ * ${客户折扣}$', '0', '2025-04-08 17:37:44'); +INSERT INTO `accounting_config` VALUES ('accc-83', '算力劵交易', '算力券-付费-代付费', '客户所在机构', '贷', '本机构', '底价收入', '${交易金额}$ * (1 - ${客户折扣}$)', '0', '2025-04-08 17:37:44'); +INSERT INTO `accounting_config` VALUES ('accc-84', '算力劵交易', '算力券-付费-代付费', '客户所在机构', '贷', '供应商', '待结转底价销售收入', '${交易金额}$ * ${客户折扣}$', '0', '2025-04-08 17:37:44'); +INSERT INTO `accounting_config` VALUES ('accc-85', '算力劵交易', '算力券-付费-代付费-代付费', '分销商机构', '借', '本机构', '算力劵临时账户', '${交易金额}$', '0', '2025-04-08 17:37:44'); +INSERT INTO `accounting_config` VALUES ('accc-86', '算力劵交易', '算力券-付费-代付费-代付费', '分销商机构', '贷', '供应商', '算力劵临时账户', '${交易金额}$', '0', '2025-04-08 17:37:44'); +INSERT INTO `accounting_config` VALUES ('accc-87', '算力劵交易', '算力券-付费-代付费-代付费', '分销商机构', '贷', '本机构', '底价收入', '${交易金额}$', '0', '2025-04-08 17:37:44'); +INSERT INTO `accounting_config` VALUES ('accc-88', '算力劵交易', '算力券-付费-代付费-代付费', '分销商机构', '贷', '供应商', '待结转底价销售收入', '${交易金额}$', '0', '2025-04-08 17:37:44'); +INSERT INTO `accounting_config` VALUES ('accc-89', '算力劵交易', '算力券-付费-代付费-代付费', '分销商机构', '借', '分销商', '分销商存放资金', '${交易金额}$', '0', '2025-04-08 17:37:44'); + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/b/all_table.sql b/b/all_table.sql index a5f4c7c..9b8c31e 100644 --- a/b/all_table.sql +++ b/b/all_table.sql @@ -2396,6 +2396,67 @@ CREATE TABLE `subject` ( PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '科目表' ROW_FORMAT = Dynamic; +-- ---------------------------- +-- Table structure for finance_settlement +-- ---------------------------- +DROP TABLE IF EXISTS `finance_settlement`; +CREATE TABLE `finance_settlement` ( + `id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'id', + `settlement_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '结算单号', + `accounting_orgid` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账本机构id', + `counterparty_type` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '对手方类型 supplier/reseller', + `counterparty_orgid` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '对手方机构id', + `counterparty_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '对手方名称', + `period_type` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账期类型 day/month', + `period_start` date NULL DEFAULT NULL COMMENT '账期开始日期', + `period_end` date NULL DEFAULT NULL COMMENT '账期结束日期', + `sales_amount` double(18, 8) NULL DEFAULT 0.00000000 COMMENT '销售金额', + `settlement_amount` double(18, 8) NULL DEFAULT 0.00000000 COMMENT '结算金额', + `platform_income_amount` double(18, 8) NULL DEFAULT 0.00000000 COMMENT '平台收入', + `bill_count` int(11) NULL DEFAULT 0 COMMENT '账单数量', + `status` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'draft' COMMENT 'draft/approving/approved/rejected/settled/failed/cancelled', + `approval_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '审批id', + `approval_status` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '审批状态', + `failure_reason` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '失败原因', + `settled_at` datetime NULL DEFAULT NULL COMMENT '结算记账时间', + `created_by` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建人', + `del_flg` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志', + `create_at` datetime NULL DEFAULT current_timestamp() COMMENT '创建时间', + `update_at` datetime NULL DEFAULT current_timestamp() COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `finance_settlement_un`(`accounting_orgid`, `counterparty_type`, `counterparty_orgid`, `period_start`, `period_end`, `del_flg`) USING BTREE, + INDEX `finance_settlement_idx1`(`accounting_orgid`, `status`) USING BTREE, + INDEX `finance_settlement_idx2`(`counterparty_type`, `counterparty_orgid`) USING BTREE, + INDEX `finance_settlement_idx3`(`period_start`, `period_end`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '财务结算单主表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for finance_settlement_detail +-- ---------------------------- +DROP TABLE IF EXISTS `finance_settlement_detail`; +CREATE TABLE `finance_settlement_detail` ( + `id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'id', + `settlement_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '结算单id', + `bill_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账单id', + `order_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '订单id', + `bill_date` date NULL DEFAULT NULL COMMENT '账单日期', + `customerid` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '客户id', + `providerid` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '供应商机构id', + `productid` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '产品id', + `business_op` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '业务操作', + `sale_mode` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '销售模式 0折扣/1代付费/2底价', + `sales_amount` double(18, 8) NULL DEFAULT 0.00000000 COMMENT '销售金额', + `settlement_amount` double(18, 8) NULL DEFAULT 0.00000000 COMMENT '结算金额', + `platform_income_amount` double(18, 8) NULL DEFAULT 0.00000000 COMMENT '平台收入', + `amount_source` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'bill_detail' COMMENT '金额来源', + `del_flg` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志', + `create_at` datetime NULL DEFAULT current_timestamp() COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `finance_settlement_detail_idx1`(`settlement_id`) USING BTREE, + INDEX `finance_settlement_detail_idx2`(`bill_id`) USING BTREE, + INDEX `finance_settlement_detail_idx3`(`providerid`, `productid`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '财务结算单明细快照' ROW_FORMAT = Dynamic; + -- ---------------------------- -- Table structure for transfer_record -- ---------------------------- diff --git a/b/bill/finance_settlement_apv_callback.dspy b/b/bill/finance_settlement_apv_callback.dspy new file mode 100644 index 0000000..0d0ea74 --- /dev/null +++ b/b/bill/finance_settlement_apv_callback.dspy @@ -0,0 +1,301 @@ +# -*- coding: utf-8 -*- +"""财务结算审批回调。""" + +import datetime + +DBNAME = 'kboss' + + +def _period_settle_mode(period_type): + if period_type == 'day': + return '1' + if period_type == 'month': + return '3' + return '0' + + +async def _business_date(): + try: + return await get_business_date(sor=None) + except Exception: + return datetime.datetime.now().strftime('%Y-%m-%d') + + +async def _settle_supplier(sor, settlement): + rows = await sor.sqlExe(""" + SELECT sale_mode, SUM(settlement_amount) AS amount + FROM finance_settlement_detail + WHERE settlement_id=${settlement_id}$ + AND del_flg='0' + GROUP BY sale_mode + """, {'settlement_id': settlement['id']}) + settle_date = await _business_date() + settle_mode = _period_settle_mode(settlement.get('period_type')) + failures = [] + for row in rows: + amount = round(float(row.get('amount') or 0), 8) + if amount == 0: + continue + sale_mode = row.get('sale_mode') or '0' + try: + settle_log = { + 'accounting_orgid': settlement.get('accounting_orgid'), + 'providerid': settlement.get('counterparty_orgid'), + 'settle_date': settle_date, + 'settle_mode': settle_mode, + 'sale_mode': sale_mode, + 'settle_amt': amount, + 'business_op': 'SETTLE', + } + ai = SettleAccounting(settle_log) + await ai.accounting(sor) + except Exception as e: + failures.append('sale_mode=%s: %s' % (sale_mode, str(e))) + if failures: + return False, '; '.join(failures) + return True, None + + +async def _get_account(sor, accounting_orgid, orgid, subjectname): + rows = await sor.sqlExe(""" + SELECT a.* + FROM account a + INNER JOIN subject s ON a.subjectid=s.id + WHERE a.accounting_orgid=${accounting_orgid}$ + AND a.orgid=${orgid}$ + AND s.name=${subjectname}$ + AND a.del_flg='0' + AND s.del_flg='0' + LIMIT 1 + """, { + 'accounting_orgid': accounting_orgid, + 'orgid': orgid, + 'subjectname': subjectname, + }) + return rows[0] if rows else None + + +async def _latest_balance(sor, accountid): + rows = await sor.sqlExe(""" + SELECT balance + FROM acc_balance + WHERE accountid=${accountid}$ AND del_flg='0' + ORDER BY acc_date DESC + LIMIT 1 + """, {'accountid': accountid}) + if not rows or rows[0].get('balance') is None: + return 0.0 + return float(rows[0].get('balance') or 0) + + +def _next_balance(account, balance, acc_dir, amount): + balance_at = account.get('balance_at') + if ( + (balance_at == '0' and acc_dir == '1') + or (balance_at == '1' and acc_dir == '0') + ): + return balance - amount + return balance + amount + + +async def _write_balance(sor, accountid, acc_date, balance): + rows = await sor.sqlExe(""" + SELECT id + FROM acc_balance + WHERE accountid=${accountid}$ AND acc_date=${acc_date}$ AND del_flg='0' + LIMIT 1 + """, {'accountid': accountid, 'acc_date': acc_date}) + if rows: + await sor.U('acc_balance', { + 'id': rows[0]['id'], + 'balance': balance, + }) + else: + await sor.C('acc_balance', { + 'id': uuid(), + 'accountid': accountid, + 'acc_date': acc_date, + 'balance': balance, + 'del_flg': '0', + 'create_at': datetime.datetime.now(), + }) + + +async def _write_reseller_leg( + sor, + billid, + acc_date, + accounting_orgid, + participantid, + participanttype, + subjectname, + accounting_dir, + amount, + description, +): + account = await _get_account(sor, accounting_orgid, participantid, subjectname) + if not account: + raise Exception( + '未找到账户 accounting_orgid=%s, orgid=%s, subject=%s' + % (accounting_orgid, participantid, subjectname) + ) + acc_dir = '0' if accounting_dir == '借' else '1' + old_balance = await _latest_balance(sor, account['id']) + new_balance = _next_balance(account, old_balance, acc_dir, amount) + await _write_balance(sor, account['id'], acc_date, new_balance) + await sor.C('bill_detail', { + 'id': uuid(), + 'accounting_orgid': accounting_orgid, + 'billid': billid, + 'description': description, + 'participantid': participantid, + 'participanttype': participanttype, + 'subjectname': subjectname, + 'accounting_dir': accounting_dir, + 'amount': amount, + 'del_flg': '0', + 'create_at': datetime.datetime.now(), + }) + logid = uuid() + await sor.C('accounting_log', { + 'id': logid, + 'accountid': account['id'], + 'acc_date': acc_date, + 'acc_timestamp': datetime.datetime.now(), + 'acc_dir': acc_dir, + 'summary': 'SETTLE', + 'amount': amount, + 'billid': billid, + 'del_flg': '0', + 'create_at': datetime.datetime.now(), + }) + await sor.C('acc_detail', { + 'id': uuid(), + 'accountid': account['id'], + 'acc_date': acc_date, + 'acc_timestamp': datetime.datetime.now(), + 'acc_dir': acc_dir, + 'summary': 'SETTLE', + 'amount': amount, + 'balance': new_balance, + 'acclogid': logid, + 'del_flg': '0', + 'create_at': datetime.datetime.now(), + }) + + +async def _settle_reseller(sor, settlement): + amount = round(float(settlement.get('settlement_amount') or 0), 8) + if amount == 0: + return True, None + if amount < 0: + return False, '分销商结算金额为负数,需先人工确认红冲口径' + + accounting_orgid = settlement.get('accounting_orgid') + reseller_orgid = settlement.get('counterparty_orgid') + settle_date = await _business_date() + billid = uuid() + description = '分销商结算-%s' % settlement.get('settlement_no') + + await sor.C('bill', { + 'id': billid, + 'customerid': reseller_orgid, + 'business_op': 'SETTLE', + 'amount': amount, + 'bill_date': settle_date, + 'bill_timestamp': datetime.datetime.now(), + 'bill_state': '1', + 'del_flg': '0', + 'create_at': datetime.datetime.now(), + }) + + # 结算分录:借记分销商存放资金,贷记本机构资金账号。 + await _write_reseller_leg( + sor, + billid, + settle_date, + accounting_orgid, + reseller_orgid, + '分销商', + '分销商存放资金', + '借', + amount, + description, + ) + await _write_reseller_leg( + sor, + billid, + settle_date, + accounting_orgid, + accounting_orgid, + '本机构', + '资金账号', + '贷', + amount, + description, + ) + return True, None + + +async def finance_settlement_apv_callback(ns={}): + approval_id = ns.get('approval_id') or ns.get('apv_id') + status = ns.get('status') + if not approval_id or not status: + return {'status': False, 'msg': '缺少 approval_id/apv_id 或 status'} + status_map = { + 'start': 'approving', + 'agree': 'approved', + 'refuse': 'rejected', + 'terminate': 'cancelled', + } + next_status = status_map.get(status, status) + db = DBPools() + async with db.sqlorContext(DBNAME) as sor: + try: + rows = await sor.R('finance_settlement', {'approval_id': approval_id, 'del_flg': '0'}) + if not rows: + return {'status': False, 'msg': '未找到结算审批数据'} + now_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + results = [] + for settlement in rows: + update_data = { + 'id': settlement['id'], + 'approval_status': status, + 'status': next_status, + 'update_at': now_time, + } + if status != 'agree': + await sor.U('finance_settlement', update_data) + results.append({'settlement_id': settlement['id'], 'status': next_status}) + continue + + if settlement.get('status') == 'settled': + results.append({'settlement_id': settlement['id'], 'status': 'settled'}) + continue + + if settlement.get('counterparty_type') == 'supplier': + ok, err = await _settle_supplier(sor, settlement) + else: + ok, err = await _settle_reseller(sor, settlement) + + if ok: + update_data['status'] = 'settled' + update_data['settled_at'] = now_time + update_data['failure_reason'] = None + results.append({'settlement_id': settlement['id'], 'status': 'settled'}) + else: + update_data['status'] = 'failed' + update_data['failure_reason'] = err + results.append({ + 'settlement_id': settlement['id'], + 'status': 'failed', + 'failure_reason': err, + }) + await sor.U('finance_settlement', update_data) + return {'status': True, 'msg': 'ok', 'data': results} + except Exception as e: + return {'status': False, 'msg': '审批回调处理失败, %s' % str(e)} + + +ret = await finance_settlement_apv_callback(params_kw) +return ret diff --git a/b/bill/finance_settlement_create.dspy b/b/bill/finance_settlement_create.dspy new file mode 100644 index 0000000..cdb281d --- /dev/null +++ b/b/bill/finance_settlement_create.dspy @@ -0,0 +1,283 @@ +# -*- coding: utf-8 -*- +"""创建财务结算单并保存明细快照。""" + +import datetime + +DBNAME = 'kboss' + + +def _round_money(v): + return round(float(v or 0), 8) + + +def _is_reverse_op(op): + return str(op or '').upper().endswith('_REVERSE') + + +def _sale_mode_from_subject(subjectname): + text = str(subjectname or '') + if '代付费' in text or '返佣' in text: + return '1' + if '底价' in text or '低价' in text: + return '2' + return '0' + + +def _validate_params(ns): + for key in ('accounting_orgid', 'counterparty_type', 'counterparty_orgid'): + if not ns.get(key): + return None, '缺少 %s' % key + if ns.get('counterparty_type') not in ('supplier', 'reseller'): + return None, 'counterparty_type 仅支持 supplier / reseller' + period_type = ns.get('period_type') or 'day' + if period_type not in ('day', 'month'): + return None, 'period_type 仅支持 day / month' + period_start = ns.get('period_start') or ns.get('start_date') + period_end = ns.get('period_end') or ns.get('end_date') + if not period_start or not period_end: + return None, '缺少 period_start / period_end' + return { + 'accounting_orgid': ns.get('accounting_orgid'), + 'counterparty_type': ns.get('counterparty_type'), + 'counterparty_orgid': ns.get('counterparty_orgid'), + 'period_type': period_type, + 'period_start': period_start, + 'period_end': period_end, + 'userid': ns.get('userid') or ns.get('user_id'), + }, None + + +async def _ensure_schema(sor): + await sor.sqlExe(""" + CREATE TABLE IF NOT EXISTS finance_settlement ( + id varchar(32) NOT NULL, + settlement_no varchar(64), + accounting_orgid varchar(32), + counterparty_type varchar(16), + counterparty_orgid varchar(32), + counterparty_name varchar(255), + period_type varchar(16), + period_start date, + period_end date, + sales_amount double(18,8) DEFAULT 0, + settlement_amount double(18,8) DEFAULT 0, + platform_income_amount double(18,8) DEFAULT 0, + bill_count int DEFAULT 0, + status varchar(16) DEFAULT 'draft', + approval_id varchar(64), + approval_status varchar(16), + failure_reason varchar(1000), + settled_at datetime, + created_by varchar(32), + del_flg varchar(1) DEFAULT '0', + create_at datetime DEFAULT CURRENT_TIMESTAMP, + update_at datetime DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + UNIQUE KEY finance_settlement_un (accounting_orgid, counterparty_type, counterparty_orgid, period_start, period_end, del_flg) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + """, {}) + await sor.sqlExe(""" + CREATE TABLE IF NOT EXISTS finance_settlement_detail ( + id varchar(32) NOT NULL, + settlement_id varchar(32), + bill_id varchar(32), + order_id varchar(32), + bill_date date, + customerid varchar(32), + providerid varchar(32), + productid varchar(32), + business_op varchar(64), + sale_mode varchar(1), + sales_amount double(18,8) DEFAULT 0, + settlement_amount double(18,8) DEFAULT 0, + platform_income_amount double(18,8) DEFAULT 0, + amount_source varchar(32) DEFAULT 'bill_detail', + del_flg varchar(1) DEFAULT '0', + create_at datetime DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + KEY finance_settlement_detail_idx1 (settlement_id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + """, {}) + + +async def _existing_settlement(sor, args): + rows = await sor.sqlExe(""" + SELECT id, settlement_no, status + FROM finance_settlement + WHERE accounting_orgid=${accounting_orgid}$ + AND counterparty_type=${counterparty_type}$ + AND counterparty_orgid=${counterparty_orgid}$ + AND period_start=${period_start}$ + AND period_end=${period_end}$ + AND del_flg='0' + LIMIT 1 + """, args) + return rows[0] if rows else None + + +async def _counterparty_name(sor, args): + if args['counterparty_type'] == 'supplier': + rows = await sor.sqlExe(""" + SELECT COALESCE(p.name, o.orgname) AS name + FROM organization o + LEFT JOIN provider p ON p.orgid=o.id AND p.del_flg='0' + WHERE o.id=${counterparty_orgid}$ AND o.del_flg='0' + LIMIT 1 + """, args) + else: + rows = await sor.sqlExe(""" + SELECT orgname AS name FROM organization + WHERE id=${counterparty_orgid}$ AND del_flg='0' + LIMIT 1 + """, args) + return rows[0].get('name') if rows else None + + +async def _fetch_source_rows(sor, args): + filters = [ + "b.del_flg='0'", + "b.bill_state='1'", + "b.bill_date >= ${period_start}$", + "b.bill_date <= ${period_end}$", + ] + if args['counterparty_type'] == 'supplier': + filters.append('b.providerid=${counterparty_orgid}$') + settlement_join = """ + LEFT JOIN bill_detail sd ON sd.billid=b.id + AND sd.accounting_orgid=${accounting_orgid}$ + AND sd.del_flg='0' + AND sd.accounting_dir='贷' + AND sd.subjectname LIKE '待结转%' + """ + else: + filters.append('cust.parentid=${counterparty_orgid}$') + settlement_join = """ + LEFT JOIN bill_detail sd ON sd.billid=b.id + AND sd.accounting_orgid=${accounting_orgid}$ + AND sd.del_flg='0' + AND sd.accounting_dir='借' + AND sd.subjectname='分销商存放资金' + AND sd.participantid=cust.parentid + """ + + sql = """ + SELECT b.id AS bill_id, b.orderid AS order_id, b.bill_date, + b.customerid, b.providerid, b.productid, b.business_op, + b.amount AS sales_amount, + COALESCE(sd.amount, 0) AS settlement_amount, + sd.subjectname AS settlement_subject, + COALESCE(( + SELECT SUM(bd.amount) + FROM bill_detail bd + WHERE bd.billid=b.id + AND bd.accounting_orgid=${accounting_orgid}$ + AND bd.del_flg='0' + AND bd.accounting_dir='贷' + AND bd.subjectname IN ('折扣收入', '底价收入') + ), 0) AS platform_income_amount + FROM bill b + INNER JOIN organization cust ON cust.id=b.customerid AND cust.del_flg='0' + %s + WHERE %s + ORDER BY b.bill_date DESC, b.create_at DESC + """ % (settlement_join, ' AND '.join(filters)) + rows = await sor.sqlExe(sql, args) + items = [] + seen_platform_bill = set() + for row in rows: + sign = -1 if _is_reverse_op(row.get('business_op')) else 1 + bill_id = row.get('bill_id') + platform_income = float(row.get('platform_income_amount') or 0) + if bill_id in seen_platform_bill: + platform_income = 0 + seen_platform_bill.add(bill_id) + items.append({ + 'bill_id': bill_id, + 'order_id': row.get('order_id'), + 'bill_date': str(row.get('bill_date'))[:10] if row.get('bill_date') else None, + 'customerid': row.get('customerid'), + 'providerid': row.get('providerid'), + 'productid': row.get('productid'), + 'business_op': row.get('business_op'), + 'sale_mode': _sale_mode_from_subject(row.get('settlement_subject')), + 'sales_amount': _round_money(sign * float(row.get('sales_amount') or 0)), + 'settlement_amount': _round_money(sign * float(row.get('settlement_amount') or 0)), + 'platform_income_amount': _round_money(sign * platform_income), + 'amount_source': 'bill_detail', + }) + return items + + +def _summary(items): + return { + 'sales_amount': _round_money(sum(i.get('sales_amount') or 0 for i in items)), + 'settlement_amount': _round_money(sum(i.get('settlement_amount') or 0 for i in items)), + 'platform_income_amount': _round_money(sum(i.get('platform_income_amount') or 0 for i in items)), + 'bill_count': len({i.get('bill_id') for i in items if i.get('bill_id')}), + } + + +async def finance_settlement_create(ns={}): + args, err = _validate_params(ns) + if err: + return {'status': False, 'msg': err} + db = DBPools() + async with db.sqlorContext(DBNAME) as sor: + try: + await _ensure_schema(sor) + existing = await _existing_settlement(sor, args) + if existing: + return {'status': False, 'msg': '该账期已存在结算单', 'data': existing} + items = await _fetch_source_rows(sor, args) + if not items: + return {'status': False, 'msg': '该账期无可结算已记账账单'} + summary = _summary(items) + settlement_id = uuid() + settlement_no = 'FS%s%s' % ( + datetime.datetime.now().strftime('%Y%m%d%H%M%S'), + str(settlement_id)[-6:], + ) + master = { + 'id': settlement_id, + 'settlement_no': settlement_no, + 'accounting_orgid': args['accounting_orgid'], + 'counterparty_type': args['counterparty_type'], + 'counterparty_orgid': args['counterparty_orgid'], + 'counterparty_name': await _counterparty_name(sor, args), + 'period_type': args['period_type'], + 'period_start': args['period_start'], + 'period_end': args['period_end'], + 'sales_amount': summary['sales_amount'], + 'settlement_amount': summary['settlement_amount'], + 'platform_income_amount': summary['platform_income_amount'], + 'bill_count': summary['bill_count'], + 'status': 'draft', + 'created_by': args.get('userid'), + 'del_flg': '0', + 'create_at': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), + 'update_at': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), + } + await sor.C('finance_settlement', master) + for item in items: + detail = dict(item) + detail['id'] = uuid() + detail['settlement_id'] = settlement_id + detail['del_flg'] = '0' + detail['create_at'] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + await sor.C('finance_settlement_detail', detail) + return { + 'status': True, + 'msg': 'ok', + 'data': { + 'settlement_id': settlement_id, + 'settlement_no': settlement_no, + 'status': 'draft', + 'summary': summary, + }, + } + except Exception as e: + return {'status': False, 'msg': '创建结算单失败, %s' % str(e)} + + +ret = await finance_settlement_create(params_kw) +return ret diff --git a/b/bill/finance_settlement_detail.dspy b/b/bill/finance_settlement_detail.dspy new file mode 100644 index 0000000..d0d196b --- /dev/null +++ b/b/bill/finance_settlement_detail.dspy @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +"""财务结算单详情查询。""" + +DBNAME = 'kboss' + + +def _parse_page(ns, default_page=1, default_size=100, max_size=500): + try: + current_page = int(ns.get('current_page', default_page) or default_page) + except (TypeError, ValueError): + current_page = default_page + try: + page_size = int(ns.get('page_size', default_size) or default_size) + except (TypeError, ValueError): + page_size = default_size + current_page = max(1, current_page) + page_size = max(1, min(page_size, max_size)) + return current_page, page_size, (current_page - 1) * page_size + + +async def finance_settlement_detail(ns={}): + settlement_id = ns.get('settlement_id') or ns.get('id') + if not settlement_id: + return {'status': False, 'msg': '缺少 settlement_id'} + current_page, page_size, offset = _parse_page(ns) + db = DBPools() + async with db.sqlorContext(DBNAME) as sor: + try: + rows = await sor.R('finance_settlement', {'id': settlement_id, 'del_flg': '0'}) + if not rows: + return {'status': False, 'msg': '结算单不存在'} + count_rows = await sor.sqlExe(""" + SELECT COUNT(*) AS total_count + FROM finance_settlement_detail + WHERE settlement_id=${settlement_id}$ AND del_flg='0' + """, {'settlement_id': settlement_id}) + total_count = count_rows[0]['total_count'] if count_rows else 0 + detail_rows = await sor.sqlExe(""" + SELECT * + FROM finance_settlement_detail + WHERE settlement_id=${settlement_id}$ AND del_flg='0' + ORDER BY bill_date DESC, create_at DESC + LIMIT %d OFFSET %d + """ % (page_size, offset), {'settlement_id': settlement_id}) + return { + 'status': True, + 'msg': 'ok', + 'data': { + 'settlement': rows[0], + 'detail_total_count': total_count, + 'current_page': current_page, + 'page_size': page_size, + 'items': detail_rows, + }, + } + except Exception as e: + return {'status': False, 'msg': '查询结算单详情失败, %s' % str(e)} + + +ret = await finance_settlement_detail(params_kw) +return ret diff --git a/b/bill/finance_settlement_list.dspy b/b/bill/finance_settlement_list.dspy new file mode 100644 index 0000000..8b38352 --- /dev/null +++ b/b/bill/finance_settlement_list.dspy @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +"""财务结算单列表查询。""" + +DBNAME = 'kboss' + + +def _parse_page(ns, default_page=1, default_size=20, max_size=200): + try: + current_page = int(ns.get('current_page', default_page) or default_page) + except (TypeError, ValueError): + current_page = default_page + try: + page_size = int(ns.get('page_size', default_size) or default_size) + except (TypeError, ValueError): + page_size = default_size + current_page = max(1, current_page) + page_size = max(1, min(page_size, max_size)) + return current_page, page_size, (current_page - 1) * page_size + + +async def finance_settlement_list(ns={}): + accounting_orgid = ns.get('accounting_orgid') + if not accounting_orgid: + return {'status': False, 'msg': '缺少 accounting_orgid'} + current_page, page_size, offset = _parse_page(ns) + conditions = ["accounting_orgid=${accounting_orgid}$", "del_flg='0'"] + params = {'accounting_orgid': accounting_orgid} + for key in ('counterparty_type', 'counterparty_orgid', 'status', 'period_type'): + if ns.get(key): + conditions.append('%s=${%s}$' % (key, key)) + params[key] = ns[key] + if ns.get('start_date'): + conditions.append('period_end >= ${start_date}$') + params['start_date'] = ns['start_date'] + if ns.get('end_date'): + conditions.append('period_start <= ${end_date}$') + params['end_date'] = ns['end_date'] + where_sql = ' AND '.join(conditions) + db = DBPools() + async with db.sqlorContext(DBNAME) as sor: + try: + count_rows = await sor.sqlExe( + 'SELECT COUNT(*) AS total_count FROM finance_settlement WHERE %s' % where_sql, + params, + ) + total_count = count_rows[0]['total_count'] if count_rows else 0 + sql = """ + SELECT * + FROM finance_settlement + WHERE %s + ORDER BY period_end DESC, create_at DESC + LIMIT %d OFFSET %d + """ % (where_sql, page_size, offset) + rows = await sor.sqlExe(sql, params) + return { + 'status': True, + 'msg': 'ok', + 'data': { + 'total_count': total_count, + 'current_page': current_page, + 'page_size': page_size, + 'items': rows, + }, + } + except Exception as e: + return {'status': False, 'msg': '查询结算单失败, %s' % str(e)} + + +ret = await finance_settlement_list(params_kw) +return ret diff --git a/b/bill/finance_settlement_migration.sql b/b/bill/finance_settlement_migration.sql new file mode 100644 index 0000000..54487c7 --- /dev/null +++ b/b/bill/finance_settlement_migration.sql @@ -0,0 +1,89 @@ +/* +财务结算接口手动迁移 SQL。 + +执行范围: +1. 新增统一结算主表 finance_settlement。 +2. 新增统一结算明细表 finance_settlement_detail。 +3. 如果表已经由接口自动创建,补齐 sale_mode 字段。 + +注意: +- 不修改旧 provider_settle_data 流程。 +- 分销商结算记账依赖既有账户: + account(accounting_orgid=上级机构, orgid=分销商机构, subject=分销商存放资金) + account(accounting_orgid=上级机构, orgid=上级机构, subject=资金账号) +*/ + +CREATE TABLE IF NOT EXISTS `finance_settlement` ( + `id` varchar(32) NOT NULL COMMENT 'id', + `settlement_no` varchar(64) DEFAULT NULL COMMENT '结算单号', + `accounting_orgid` varchar(32) DEFAULT NULL COMMENT '账本机构id', + `counterparty_type` varchar(16) DEFAULT NULL COMMENT '对手方类型 supplier/reseller', + `counterparty_orgid` varchar(32) DEFAULT NULL COMMENT '对手方机构id', + `counterparty_name` varchar(255) DEFAULT NULL COMMENT '对手方名称', + `period_type` varchar(16) DEFAULT NULL COMMENT '账期类型 day/month', + `period_start` date DEFAULT NULL COMMENT '账期开始日期', + `period_end` date DEFAULT NULL COMMENT '账期结束日期', + `sales_amount` double(18,8) DEFAULT 0.00000000 COMMENT '销售金额', + `settlement_amount` double(18,8) DEFAULT 0.00000000 COMMENT '结算金额', + `platform_income_amount` double(18,8) DEFAULT 0.00000000 COMMENT '平台收入', + `bill_count` int(11) DEFAULT 0 COMMENT '账单数量', + `status` varchar(16) DEFAULT 'draft' COMMENT 'draft/approving/approved/rejected/settled/failed/cancelled', + `approval_id` varchar(64) DEFAULT NULL COMMENT '审批id', + `approval_status` varchar(16) DEFAULT NULL COMMENT '审批状态', + `failure_reason` varchar(1000) DEFAULT NULL COMMENT '失败原因', + `settled_at` datetime DEFAULT NULL COMMENT '结算记账时间', + `created_by` varchar(32) DEFAULT NULL COMMENT '创建人', + `del_flg` varchar(1) DEFAULT '0' COMMENT '删除标志', + `create_at` datetime DEFAULT current_timestamp() COMMENT '创建时间', + `update_at` datetime DEFAULT current_timestamp() COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `finance_settlement_un` (`accounting_orgid`, `counterparty_type`, `counterparty_orgid`, `period_start`, `period_end`, `del_flg`), + KEY `finance_settlement_idx1` (`accounting_orgid`, `status`), + KEY `finance_settlement_idx2` (`counterparty_type`, `counterparty_orgid`), + KEY `finance_settlement_idx3` (`period_start`, `period_end`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='财务结算单主表'; + +CREATE TABLE IF NOT EXISTS `finance_settlement_detail` ( + `id` varchar(32) NOT NULL COMMENT 'id', + `settlement_id` varchar(32) DEFAULT NULL COMMENT '结算单id', + `bill_id` varchar(32) DEFAULT NULL COMMENT '账单id', + `order_id` varchar(32) DEFAULT NULL COMMENT '订单id', + `bill_date` date DEFAULT NULL COMMENT '账单日期', + `customerid` varchar(32) DEFAULT NULL COMMENT '客户id', + `providerid` varchar(32) DEFAULT NULL COMMENT '供应商机构id', + `productid` varchar(32) DEFAULT NULL COMMENT '产品id', + `business_op` varchar(64) DEFAULT NULL COMMENT '业务操作', + `sale_mode` varchar(1) DEFAULT NULL COMMENT '销售模式 0折扣/1代付费/2底价', + `sales_amount` double(18,8) DEFAULT 0.00000000 COMMENT '销售金额', + `settlement_amount` double(18,8) DEFAULT 0.00000000 COMMENT '结算金额', + `platform_income_amount` double(18,8) DEFAULT 0.00000000 COMMENT '平台收入', + `amount_source` varchar(32) DEFAULT 'bill_detail' COMMENT '金额来源', + `del_flg` varchar(1) DEFAULT '0' COMMENT '删除标志', + `create_at` datetime DEFAULT current_timestamp() COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `finance_settlement_detail_idx1` (`settlement_id`), + KEY `finance_settlement_detail_idx2` (`bill_id`), + KEY `finance_settlement_detail_idx3` (`providerid`, `productid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='财务结算单明细快照'; + +ALTER TABLE `finance_settlement_detail` + ADD COLUMN IF NOT EXISTS `sale_mode` varchar(1) DEFAULT NULL COMMENT '销售模式 0折扣/1代付费/2底价' AFTER `business_op`; + +-- 执行分销商结算前,可用以下 SQL 检查目标账户是否存在: +-- SELECT a.* +-- FROM account a +-- JOIN subject s ON a.subjectid=s.id +-- WHERE a.accounting_orgid='<上级机构orgid>' +-- AND a.orgid='<分销商orgid>' +-- AND s.name='分销商存放资金' +-- AND a.del_flg='0' +-- AND s.del_flg='0'; +-- +-- SELECT a.* +-- FROM account a +-- JOIN subject s ON a.subjectid=s.id +-- WHERE a.accounting_orgid='<上级机构orgid>' +-- AND a.orgid='<上级机构orgid>' +-- AND s.name='资金账号' +-- AND a.del_flg='0' +-- AND s.del_flg='0'; diff --git a/b/bill/finance_settlement_preview.dspy b/b/bill/finance_settlement_preview.dspy new file mode 100644 index 0000000..131cb36 --- /dev/null +++ b/b/bill/finance_settlement_preview.dspy @@ -0,0 +1,281 @@ +# -*- coding: utf-8 -*- +"""财务结算单生成前预览。""" + +DBNAME = 'kboss' + + +def _round_money(v): + return round(float(v or 0), 8) + + +def _is_reverse_op(op): + return str(op or '').upper().endswith('_REVERSE') + + +def _sale_mode_from_subject(subjectname): + text = str(subjectname or '') + if '代付费' in text or '返佣' in text: + return '1' + if '底价' in text or '低价' in text: + return '2' + return '0' + + +def _parse_page(ns, default_page=1, default_size=50, max_size=500): + try: + current_page = int(ns.get('current_page', default_page) or default_page) + except (TypeError, ValueError): + current_page = default_page + try: + page_size = int(ns.get('page_size', default_size) or default_size) + except (TypeError, ValueError): + page_size = default_size + current_page = max(1, current_page) + page_size = max(1, min(page_size, max_size)) + return current_page, page_size, (current_page - 1) * page_size + + +def _validate_params(ns): + required = ('accounting_orgid', 'counterparty_type', 'counterparty_orgid') + for key in required: + if not ns.get(key): + return None, '缺少 %s' % key + counterparty_type = ns.get('counterparty_type') + if counterparty_type not in ('supplier', 'reseller'): + return None, 'counterparty_type 仅支持 supplier / reseller' + period_type = ns.get('period_type') or 'day' + if period_type not in ('day', 'month'): + return None, 'period_type 仅支持 day / month' + period_start = ns.get('period_start') or ns.get('start_date') + period_end = ns.get('period_end') or ns.get('end_date') + if not period_start or not period_end: + return None, '缺少 period_start / period_end' + return { + 'accounting_orgid': ns.get('accounting_orgid'), + 'counterparty_type': counterparty_type, + 'counterparty_orgid': ns.get('counterparty_orgid'), + 'period_type': period_type, + 'period_start': period_start, + 'period_end': period_end, + }, None + + +async def _ensure_schema(sor): + await sor.sqlExe(""" + CREATE TABLE IF NOT EXISTS finance_settlement ( + id varchar(32) NOT NULL, + settlement_no varchar(64), + accounting_orgid varchar(32), + counterparty_type varchar(16), + counterparty_orgid varchar(32), + counterparty_name varchar(255), + period_type varchar(16), + period_start date, + period_end date, + sales_amount double(18,8) DEFAULT 0, + settlement_amount double(18,8) DEFAULT 0, + platform_income_amount double(18,8) DEFAULT 0, + bill_count int DEFAULT 0, + status varchar(16) DEFAULT 'draft', + approval_id varchar(64), + approval_status varchar(16), + failure_reason varchar(1000), + settled_at datetime, + created_by varchar(32), + del_flg varchar(1) DEFAULT '0', + create_at datetime DEFAULT CURRENT_TIMESTAMP, + update_at datetime DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + UNIQUE KEY finance_settlement_un (accounting_orgid, counterparty_type, counterparty_orgid, period_start, period_end, del_flg) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + """, {}) + await sor.sqlExe(""" + CREATE TABLE IF NOT EXISTS finance_settlement_detail ( + id varchar(32) NOT NULL, + settlement_id varchar(32), + bill_id varchar(32), + order_id varchar(32), + bill_date date, + customerid varchar(32), + providerid varchar(32), + productid varchar(32), + business_op varchar(64), + sale_mode varchar(1), + sales_amount double(18,8) DEFAULT 0, + settlement_amount double(18,8) DEFAULT 0, + platform_income_amount double(18,8) DEFAULT 0, + amount_source varchar(32) DEFAULT 'bill_detail', + del_flg varchar(1) DEFAULT '0', + create_at datetime DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + KEY finance_settlement_detail_idx1 (settlement_id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + """, {}) + + +async def _existing_settlement(sor, args): + rows = await sor.sqlExe(""" + SELECT id, settlement_no, status + FROM finance_settlement + WHERE accounting_orgid=${accounting_orgid}$ + AND counterparty_type=${counterparty_type}$ + AND counterparty_orgid=${counterparty_orgid}$ + AND period_start=${period_start}$ + AND period_end=${period_end}$ + AND del_flg='0' + LIMIT 1 + """, args) + return rows[0] if rows else None + + +async def _counterparty_name(sor, args): + if args['counterparty_type'] == 'supplier': + rows = await sor.sqlExe(""" + SELECT COALESCE(p.name, o.orgname) AS name + FROM organization o + LEFT JOIN provider p ON p.orgid=o.id AND p.del_flg='0' + WHERE o.id=${counterparty_orgid}$ AND o.del_flg='0' + LIMIT 1 + """, args) + else: + rows = await sor.sqlExe(""" + SELECT orgname AS name FROM organization + WHERE id=${counterparty_orgid}$ AND del_flg='0' + LIMIT 1 + """, args) + return rows[0].get('name') if rows else None + + +async def _fetch_source_rows(sor, args): + params = dict(args) + filters = [ + "b.del_flg='0'", + "b.bill_state='1'", + "b.bill_date >= ${period_start}$", + "b.bill_date <= ${period_end}$", + ] + if args['counterparty_type'] == 'supplier': + filters.append('b.providerid=${counterparty_orgid}$') + settlement_join = """ + LEFT JOIN bill_detail sd ON sd.billid=b.id + AND sd.accounting_orgid=${accounting_orgid}$ + AND sd.del_flg='0' + AND sd.accounting_dir='贷' + AND sd.subjectname LIKE '待结转%' + """ + settlement_expr = 'COALESCE(sd.amount, 0)' + sale_mode_expr = 'sd.subjectname' + else: + filters.append('cust.parentid=${counterparty_orgid}$') + settlement_join = """ + LEFT JOIN bill_detail sd ON sd.billid=b.id + AND sd.accounting_orgid=${accounting_orgid}$ + AND sd.del_flg='0' + AND sd.accounting_dir='借' + AND sd.subjectname='分销商存放资金' + AND sd.participantid=cust.parentid + """ + settlement_expr = 'COALESCE(sd.amount, 0)' + sale_mode_expr = 'sd.subjectname' + + where_sql = ' AND '.join(filters) + sql = """ + SELECT b.id AS bill_id, + b.orderid AS order_id, + b.bill_date, + b.customerid, + b.providerid, + b.productid, + b.business_op, + b.amount AS sales_amount, + %s AS settlement_amount, + %s AS settlement_subject, + COALESCE(( + SELECT SUM(bd.amount) + FROM bill_detail bd + WHERE bd.billid=b.id + AND bd.accounting_orgid=${accounting_orgid}$ + AND bd.del_flg='0' + AND bd.accounting_dir='贷' + AND bd.subjectname IN ('折扣收入', '底价收入') + ), 0) AS platform_income_amount + FROM bill b + INNER JOIN organization cust ON cust.id=b.customerid AND cust.del_flg='0' + %s + WHERE %s + ORDER BY b.bill_date DESC, b.create_at DESC + """ % (settlement_expr, sale_mode_expr, settlement_join, where_sql) + rows = await sor.sqlExe(sql, params) + items = [] + seen_platform_bill = set() + for row in rows: + sign = -1 if _is_reverse_op(row.get('business_op')) else 1 + bill_id = row.get('bill_id') + platform_income = float(row.get('platform_income_amount') or 0) + if bill_id in seen_platform_bill: + platform_income = 0 + seen_platform_bill.add(bill_id) + items.append({ + 'bill_id': bill_id, + 'order_id': row.get('order_id'), + 'bill_date': str(row.get('bill_date'))[:10] if row.get('bill_date') else None, + 'customerid': row.get('customerid'), + 'providerid': row.get('providerid'), + 'productid': row.get('productid'), + 'business_op': row.get('business_op'), + 'sale_mode': _sale_mode_from_subject(row.get('settlement_subject')), + 'sales_amount': _round_money(sign * float(row.get('sales_amount') or 0)), + 'settlement_amount': _round_money(sign * float(row.get('settlement_amount') or 0)), + 'platform_income_amount': _round_money(sign * platform_income), + 'amount_source': 'bill_detail', + }) + return items + + +def _summary(items): + return { + 'sales_amount': _round_money(sum(i.get('sales_amount') or 0 for i in items)), + 'settlement_amount': _round_money(sum(i.get('settlement_amount') or 0 for i in items)), + 'platform_income_amount': _round_money(sum(i.get('platform_income_amount') or 0 for i in items)), + 'bill_count': len({i.get('bill_id') for i in items if i.get('bill_id')}), + } + + +async def finance_settlement_preview(ns={}): + args, err = _validate_params(ns) + if err: + return {'status': False, 'msg': err} + current_page, page_size, offset = _parse_page(ns) + db = DBPools() + async with db.sqlorContext(DBNAME) as sor: + try: + await _ensure_schema(sor) + existing = await _existing_settlement(sor, args) + items = await _fetch_source_rows(sor, args) + total_count = len(items) + return { + 'status': True, + 'msg': 'ok', + 'data': { + 'accounting_orgid': args['accounting_orgid'], + 'counterparty_type': args['counterparty_type'], + 'counterparty_orgid': args['counterparty_orgid'], + 'counterparty_name': await _counterparty_name(sor, args), + 'period_type': args['period_type'], + 'period_start': args['period_start'], + 'period_end': args['period_end'], + 'existing_settlement': existing, + 'can_create': existing is None and total_count > 0, + 'summary': _summary(items), + 'total_count': total_count, + 'current_page': current_page, + 'page_size': page_size, + 'items': items[offset:offset + page_size], + }, + } + except Exception as e: + return {'status': False, 'msg': '预览失败, %s' % str(e)} + + +ret = await finance_settlement_preview(params_kw) +return ret diff --git a/b/bill/finance_settlement_submit.dspy b/b/bill/finance_settlement_submit.dspy new file mode 100644 index 0000000..903e0a1 --- /dev/null +++ b/b/bill/finance_settlement_submit.dspy @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- +"""财务结算单提交审批。""" + +import datetime +import json + +DBNAME = 'kboss' + + +async def _finance_phone(sor, orgid): + rows = await sor.sqlExe(""" + SELECT u.mobile + FROM users u + LEFT JOIN userrole ur ON u.id=ur.userid + LEFT JOIN role r ON ur.roleid=r.id + WHERE r.role='财务' + AND u.orgid=${orgid}$ + AND ur.del_flg='0' + AND r.del_flg='0' + AND u.del_flg='0' + LIMIT 1 + """, {'orgid': orgid}) + return rows[0]['mobile'] if rows else None + + +async def finance_settlement_submit(ns={}): + settlement_id = ns.get('settlement_id') or ns.get('id') + userid = ns.get('userid') or ns.get('user_id') + business_name = ns.get('business_name') or '财务结算' + if not settlement_id or not userid: + return {'status': False, 'msg': '缺少 settlement_id 或 userid'} + db = DBPools() + async with db.sqlorContext(DBNAME) as sor: + try: + rows = await sor.R('finance_settlement', {'id': settlement_id, 'del_flg': '0'}) + if not rows: + return {'status': False, 'msg': '结算单不存在'} + settlement = rows[0] + if settlement.get('status') not in ('draft', 'rejected', 'failed'): + return {'status': False, 'msg': '当前状态不可提交审批'} + phone = await _finance_phone(sor, settlement.get('accounting_orgid')) + if not phone: + return {'status': False, 'msg': '未找到当前机构财务审批人'} + business_rows = await sor.R('apv_business', {'business_name': business_name, 'del_flg': '0'}) + if not business_rows: + return {'status': False, 'msg': '审批业务不存在: %s' % business_name} + form_component = { + 'title': '财务结算审批', + 'detail': { + 'data': '%s %s-%s %s 金额:%s' % ( + settlement.get('counterparty_name'), + settlement.get('period_start'), + settlement.get('period_end'), + settlement.get('counterparty_type'), + settlement.get('settlement_amount'), + ) + }, + } + apv_json = json.dumps({ + 'table': 'finance_settlement', + 'settlement_id': settlement_id, + 'settlement_no': settlement.get('settlement_no'), + }, ensure_ascii=False) + resp = await issue_approve( + phone, + settlement.get('accounting_orgid'), + userid, + business_rows[0]['id'], + form_component, + settlement.get('counterparty_orgid'), + settlement_id, + apv_json, + ) + if not resp.get('status'): + return {'status': False, 'msg': '发起审批失败, %s' % resp.get('msg')} + now_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + await sor.U('finance_settlement', { + 'id': settlement_id, + 'status': 'approving', + 'approval_status': 'start', + 'approval_id': resp.get('instanceId'), + 'created_by': userid, + 'failure_reason': None, + 'update_at': now_time, + }) + return { + 'status': True, + 'msg': 'ok', + 'data': { + 'settlement_id': settlement_id, + 'approval_id': resp.get('instanceId'), + 'status': 'approving', + }, + } + except Exception as e: + return {'status': False, 'msg': '提交审批失败, %s' % str(e)} + + +ret = await finance_settlement_submit(params_kw) +return ret diff --git a/b/bill/finance_settlement_summary.dspy b/b/bill/finance_settlement_summary.dspy new file mode 100644 index 0000000..9cf4987 --- /dev/null +++ b/b/bill/finance_settlement_summary.dspy @@ -0,0 +1,225 @@ +# -*- coding: utf-8 -*- +""" +财务结算汇总查询。 + +按供应商或分销商聚合日结/月结应结金额、平台收入和销售额。 +金额以已记账 bill_detail 为准,不使用未记账估算。 +""" + +DBNAME = 'kboss' +INCOME_SUBJECTS = ('折扣收入', '底价收入') +PARENT_SETTLE_SUBJECT = '分销商存放资金' + + +def _round_money(v): + return round(float(v or 0), 8) + + +def _is_reverse_op(op): + return str(op or '').upper().endswith('_REVERSE') + + +def _parse_page(ns, default_page=1, default_size=20, max_size=200): + try: + current_page = int(ns.get('current_page', default_page) or default_page) + except (TypeError, ValueError): + current_page = default_page + try: + page_size = int(ns.get('page_size', default_size) or default_size) + except (TypeError, ValueError): + page_size = default_size + current_page = max(1, current_page) + page_size = max(1, min(page_size, max_size)) + return current_page, page_size, (current_page - 1) * page_size + + +def _period_key(bill_date, period_type): + text = str(bill_date or '')[:10] + if period_type == 'month': + return text[:7] + return text + + +def _validate_params(ns): + debug(ns) + accounting_orgid = ns.get('accounting_orgid') + counterparty_type = ns.get('counterparty_type') + period_type = ns.get('period_type') or 'day' + start_date = ns.get('start_date') or ns.get('period_start') + end_date = ns.get('end_date') or ns.get('period_end') + if not accounting_orgid: + return None, '缺少 accounting_orgid' + if counterparty_type not in ('supplier', 'reseller'): + return None, 'counterparty_type 仅支持 supplier / reseller' + if period_type not in ('day', 'month'): + return None, 'period_type 仅支持 day / month' + if not start_date or not end_date: + return None, '缺少 start_date / end_date' + return { + 'accounting_orgid': accounting_orgid, + 'counterparty_type': counterparty_type, + 'period_type': period_type, + 'start_date': start_date, + 'end_date': end_date, + 'counterparty_orgid': ns.get('counterparty_orgid'), + }, None + + +async def _fetch_source_rows(sor, args): + params = { + 'accounting_orgid': args['accounting_orgid'], + 'start_date': args['start_date'], + 'end_date': args['end_date'], + } + filters = [ + "b.del_flg = '0'", + "b.bill_state = '1'", + "b.bill_date >= ${start_date}$", + "b.bill_date <= ${end_date}$", + ] + if args['counterparty_type'] == 'supplier': + counterparty_expr = 'b.providerid' + name_expr = 'COALESCE(pv.name, cp.orgname)' + join_sql = """ + LEFT JOIN provider pv ON pv.orgid = b.providerid AND pv.del_flg = '0' + LEFT JOIN organization cp ON cp.id = b.providerid AND cp.del_flg = '0' + """ + settlement_expr = """ + SELECT SUM(bd.amount) + FROM bill_detail bd + WHERE bd.billid = b.id + AND bd.accounting_orgid = ${accounting_orgid}$ + AND bd.del_flg = '0' + AND bd.accounting_dir = '贷' + AND bd.subjectname LIKE '待结转%' + """ + if args.get('counterparty_orgid'): + filters.append('b.providerid = ${counterparty_orgid}$') + params['counterparty_orgid'] = args['counterparty_orgid'] + else: + counterparty_expr = 'cust.parentid' + name_expr = 'cp.orgname' + join_sql = "LEFT JOIN organization cp ON cp.id = cust.parentid AND cp.del_flg = '0'" + settlement_expr = """ + SELECT SUM(bd.amount) + FROM bill_detail bd + WHERE bd.billid = b.id + AND bd.accounting_orgid = ${accounting_orgid}$ + AND bd.del_flg = '0' + AND bd.accounting_dir = '借' + AND bd.subjectname = '分销商存放资金' + AND bd.participantid = cust.parentid + """ + filters.append("cust.parentid IS NOT NULL AND cust.parentid != ''") + if args.get('counterparty_orgid'): + filters.append('cust.parentid = ${counterparty_orgid}$') + params['counterparty_orgid'] = args['counterparty_orgid'] + + where_sql = ' AND '.join(filters) + sql = """ + SELECT b.id AS bill_id, + b.orderid AS order_id, + b.bill_date, + b.customerid, + b.providerid, + b.productid, + b.business_op, + b.amount AS sales_amount, + %s AS counterparty_orgid, + %s AS counterparty_name, + COALESCE((%s), 0) AS settlement_amount, + COALESCE(( + SELECT SUM(bd.amount) + FROM bill_detail bd + WHERE bd.billid = b.id + AND bd.accounting_orgid = ${accounting_orgid}$ + AND bd.del_flg = '0' + AND bd.accounting_dir = '贷' + AND bd.subjectname IN ('折扣收入', '底价收入') + ), 0) AS platform_income_amount + FROM bill b + INNER JOIN organization cust ON cust.id = b.customerid AND cust.del_flg = '0' + %s + WHERE %s + ORDER BY b.bill_date DESC, b.create_at DESC + """ % (counterparty_expr, name_expr, settlement_expr, join_sql, where_sql) + return await sor.sqlExe(sql, params) + + +def _aggregate_rows(rows, period_type): + buckets = {} + for row in rows: + counterparty_orgid = row.get('counterparty_orgid') + if not counterparty_orgid: + continue + period = _period_key(row.get('bill_date'), period_type) + key = (period, counterparty_orgid) + if key not in buckets: + buckets[key] = { + 'period': period, + 'counterparty_orgid': counterparty_orgid, + 'counterparty_name': row.get('counterparty_name'), + 'sales_amount': 0.0, + 'settlement_amount': 0.0, + 'platform_income_amount': 0.0, + 'bill_count': 0, + } + sign = -1 if _is_reverse_op(row.get('business_op')) else 1 + bucket = buckets[key] + bucket['sales_amount'] += sign * float(row.get('sales_amount') or 0) + bucket['settlement_amount'] += sign * float(row.get('settlement_amount') or 0) + bucket['platform_income_amount'] += sign * float(row.get('platform_income_amount') or 0) + bucket['bill_count'] += 1 + + items = [] + for item in buckets.values(): + item['sales_amount'] = _round_money(item['sales_amount']) + item['settlement_amount'] = _round_money(item['settlement_amount']) + item['platform_income_amount'] = _round_money(item['platform_income_amount']) + items.append(item) + return sorted(items, key=lambda x: (x['period'], x['settlement_amount']), reverse=True) + + +def _summary(items): + return { + 'sales_amount': _round_money(sum(i.get('sales_amount') or 0 for i in items)), + 'settlement_amount': _round_money(sum(i.get('settlement_amount') or 0 for i in items)), + 'platform_income_amount': _round_money(sum(i.get('platform_income_amount') or 0 for i in items)), + 'bill_count': sum(i.get('bill_count') or 0 for i in items), + } + + +async def finance_settlement_summary(ns={}): + args, err = _validate_params(ns) + if err: + return {'status': False, 'msg': err} + current_page, page_size, offset = _parse_page(ns) + db = DBPools() + async with db.sqlorContext(DBNAME) as sor: + try: + rows = await _fetch_source_rows(sor, args) + items = _aggregate_rows(rows, args['period_type']) + total_count = len(items) + page_items = items[offset:offset + page_size] + return { + 'status': True, + 'msg': 'ok', + 'data': { + 'accounting_orgid': args['accounting_orgid'], + 'counterparty_type': args['counterparty_type'], + 'period_type': args['period_type'], + 'start_date': args['start_date'], + 'end_date': args['end_date'], + 'summary': _summary(items), + 'total_count': total_count, + 'current_page': current_page, + 'page_size': page_size, + 'items': page_items, + }, + } + except Exception as e: + return {'status': False, 'msg': '查询失败, %s' % str(e)} + + +ret = await finance_settlement_summary(params_kw) +return ret diff --git a/b/subject.sql b/b/subject.sql new file mode 100644 index 0000000..5d17de8 --- /dev/null +++ b/b/subject.sql @@ -0,0 +1,58 @@ +/* + Navicat Premium Data Transfer + + Source Server : kbossdev + Source Server Type : MariaDB + Source Server Version : 100622 + Source Host : db:3306 + Source Schema : kboss_dev + + Target Server Type : MariaDB + Target Server Version : 100622 + File Encoding : 65001 + + Date: 18/06/2026 17:12:02 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for subject +-- ---------------------------- +DROP TABLE IF EXISTS `subject`; +CREATE TABLE `subject` ( + `id` varchar(32) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT 'id', + `name` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '科目名称', + `balance_side` varchar(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '余额方向', + `del_flg` varchar(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '0' COMMENT '删除标志', + `create_at` timestamp NOT NULL DEFAULT current_timestamp() COMMENT '创建时间戳', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '科目表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of subject +-- ---------------------------- +INSERT INTO `subject` VALUES ('subject001', '资金账号', '0', '0', '2023-08-03 17:40:34'); +INSERT INTO `subject` VALUES ('subject002', '折扣收入', '1', '0', '2023-08-03 17:40:34'); +INSERT INTO `subject` VALUES ('subject003', '底价收入', '1', '0', '2023-08-03 17:40:34'); +INSERT INTO `subject` VALUES ('subject004', '客户折扣支出', '0', '0', '2023-08-03 17:40:34'); +INSERT INTO `subject` VALUES ('subject005', '存放供应商资金', '0', '0', '2023-08-03 17:40:34'); +INSERT INTO `subject` VALUES ('subject006', '分销商代付费销售', '0', '0', '2023-08-03 17:40:34'); +INSERT INTO `subject` VALUES ('subject007', '支付宝手续费', '0', '0', '2023-08-03 17:40:34'); +INSERT INTO `subject` VALUES ('subject008', '业务账', '1', '0', '2023-08-03 17:40:34'); +INSERT INTO `subject` VALUES ('subject009', '待结转折扣销售收入', '1', '0', '2023-08-03 17:40:34'); +INSERT INTO `subject` VALUES ('subject010', '待结转代付费销售收入', '1', '0', '2023-08-03 17:40:34'); +INSERT INTO `subject` VALUES ('subject011', '待结转底价销售收入', '1', '0', '2023-08-03 17:40:34'); +INSERT INTO `subject` VALUES ('subject012', '分销商存放资金', '1', '0', '2023-08-03 17:40:34'); +INSERT INTO `subject` VALUES ('subject013', '待结转折扣支出', '1', '0', '2023-08-03 17:40:34'); +INSERT INTO `subject` VALUES ('subject014', '待结转代付费支出', '1', '0', '2023-08-03 17:40:34'); +INSERT INTO `subject` VALUES ('subject015', '待结转底价支出', '1', '0', '2023-08-03 17:40:34'); +INSERT INTO `subject` VALUES ('subject016', '代付费销售', '1', '0', '2023-08-03 17:40:34'); +INSERT INTO `subject` VALUES ('subject017', '算力券', '1', '0', '2025-03-31 11:50:49'); +INSERT INTO `subject` VALUES ('subject018', '算力券发放', '0', '0', '2025-04-08 11:20:16'); +INSERT INTO `subject` VALUES ('subject019', '算力券消费', '1', '0', '2025-04-08 11:20:32'); +INSERT INTO `subject` VALUES ('subject020', '待结算算力券', '0', '0', '2025-04-08 11:29:02'); +INSERT INTO `subject` VALUES ('subject021', '算力劵临时账户', '0', '0', '2025-04-08 17:24:30'); + +SET FOREIGN_KEY_CHECKS = 1;