Files
gzth/yuthon_property/wizard/property_statements_wizard.py
T
2026-09-02 12:01:32 +08:00

527 lines
22 KiB
Python

from odoo import models, fields, api, _, SUPERUSER_ID
from odoo.exceptions import UserError, ValidationError
from dateutil.relativedelta import relativedelta
from datetime import datetime, time, timedelta
import xlrd
import base64
import pandas as pd
import re
import logging
_logger = logging.getLogger(__name__)
class PropertyStatementsWizard(models.Model):
_name = 'property.statements.wizard'
_description = '银行流水向导弹窗'
selection_bank = fields.Selection([('js', '建设银行'), ('gs', '工商银行'), ('ms', '民生'), ('zc', '广州银行(中城)'), ('dj', '农商行(东进)')], string="选择银行", default='js')
excel_file = fields.Binary(string="上传Excel表")
excel_name = fields.Char(string="文件名")
def confirm(self):
"""
导入excel文件,按行读取数据并处理
"""
if not self.excel_file:
raise ValidationError('请上传文件。')
file_content = base64.decodebytes(self.excel_file)
# zc/dj 使用 openpyxl 或 xlrd 按银行类型区分
# 统计导入前总数
count_before = self.env['property.bank.statements'].search_count([])
if self.selection_bank in ('zc', 'dj'):
book = xlrd.open_workbook(file_contents=file_content)
sh = book.sheet_by_index(0)
if self.selection_bank == 'zc':
# 广州银行:行0-4为表头信息,第3行(索引3)读取本方账号和开户行,数据从第6行(索引6)开始
your_own_account = str(sh.row(3)[1].value).strip()
bank_branch_own = str(sh.row(3)[4].value).strip()
for rx in range(6, sh.nrows):
row = sh.row(rx)
self.property_bank_statements_zc_data(row, your_own_account, bank_branch_own)
elif self.selection_bank == 'dj':
# 农商行(东进):行0-2为表头信息,第1行(索引1)读取本方账号,数据从第4行(索引4)开始
your_own_account = str(sh.row(1)[1].value).strip()
for rx in range(4, sh.nrows):
row = sh.row(rx)
self.property_bank_statements_dj_data(row, your_own_account)
elif self.selection_bank in ('gs', 'xy'):
# 工商银行(兴源格式):xlsx文件,数据从第2行(索引1)开始
import openpyxl, io
wb = openpyxl.load_workbook(io.BytesIO(file_content))
ws = wb.active
for i, row_cells in enumerate(ws.iter_rows(values_only=True)):
if i < 1: # 跳过表头行
continue
self.property_bank_statements_xy_data_openpyxl(row_cells)
else:
book = xlrd.open_workbook(file_contents=file_content)
sh = book.sheet_by_index(0)
for rx in range(1, sh.nrows):
row = sh.row(rx)
if self.selection_bank == 'js':
self.property_bank_statements_jh_data(row)
elif self.selection_bank == 'sh':
self.property_bank_statements_sh_data(row)
elif self.selection_bank == 'ms':
self.property_bank_statements_ms_data(row)
count_after = self.env['property.bank.statements'].search_count([])
_logger.info('[银行流水导入] 银行=%s 导入前总条数=%d 导入后总条数=%d 新增=%d', self.selection_bank, count_before, count_after, count_after - count_before)
# 查询导入数据中 lending_direction 分布
loan_count = self.env['property.bank.statements'].search_count([('lending_direction', '=', 'loan')])
borrow_count = self.env['property.bank.statements'].search_count([('lending_direction', '=', 'borrow')])
none_count = self.env['property.bank.statements'].search_count([('lending_direction', '=', False)])
_logger.info('[银行流水导入] lending_direction分布: 贷(loan)=%d 借(borrow)=%d 无=%d', loan_count, borrow_count, none_count)
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': '导入成功',
'message': '银行流水数据已成功导入!',
'type': 'success',
'sticky': False,
}
}
def property_bank_statements_jh_data(self, row):
your_own_account = row[11].value
start_time = None
if row[0].value:
time_str = str(row[0].value).strip()
dt = datetime.strptime(time_str, '%Y%m%d %H:%M:%S')
dt = dt - timedelta(hours=8)
start_time = fields.Datetime.to_string(dt)
if row[2].value:
original_amount = float(row[2].value)
lending_direction = 'borrow'
elif row[3].value:
original_amount = float(row[3].value)
lending_direction = 'loan'
name_account = row[1].value
name = row[6].value
bank_branch = row[7].value
remake = row[5].value
extract = row[4].value
your_own_unit = self.classify_by_last_four(your_own_account).referred
company_id = self.env['res.company'].search([('name', '=', '集团本部')], limit=1)
if your_own_unit:
company_id = self.classify_by_last_four(your_own_account).company_id
bank_data = {
'name': name,
'your_own_unit': your_own_unit,
'no_amount': original_amount,
'company_id': company_id.id,
'bank_branch': bank_branch,
'original_amount': original_amount,
'start_time': start_time,
'your_own_account': your_own_account,
'extract': extract,
'name_account': name_account,
'remake': remake,
'lending_direction': lending_direction
}
existing_record = self.env['property.bank.statements'].search(
[('start_time', '=', start_time),
('original_amount', '=', original_amount),
('remake', '=', remake)
], limit=1
)
if not existing_record:
self.env['property.bank.statements'].create(bank_data)
def property_bank_statements_sh_data(self, row):
your_own_account = row[12].value
if row[2].value:
clean_str = row[2].value.replace('\n', ' ') # 处理后:'2025-01-17 10:10:31'
start_time = datetime.strptime(clean_str, '%Y-%m-%d %H:%M:%S')
else:
start_time = None
if row[3].value != '-':
lending_direction = 'borrow'
original_amount = float(row[3].value)
if row[4].value != '-':
lending_direction = 'loan'
original_amount = float(row[4].value)
name_account = row[5].value
name = row[6].value
remake = row[11].value
bank_branch = row[7].value
extract = row[10].value
serial_number = row[0].value
lending_direction = lending_direction
your_own_unit = self.classify_by_last_four(your_own_account).referred
company_id = self.env['res.company'].search([('name', '=', '集团本部')], limit=1)
if your_own_unit:
company_id = your_own_unit.company_id
bank_data = {
'name': name,
'serial_number': serial_number,
'your_own_unit': your_own_unit,
'company_id': company_id.id,
'bank_branch': bank_branch,
'no_amount': original_amount,
'original_amount': original_amount,
'start_time': start_time - timedelta(hours=8) ,
'your_own_account': your_own_account,
'extract': extract,
'name_account': name_account,
'remake': remake,
'lending_direction': lending_direction
}
if original_amount and serial_number:
existing_record = self.env['property.bank.statements'].search(
[('serial_number', '=', serial_number)], limit=1
)
if not existing_record:
self.env['property.bank.statements'].create(bank_data)
else:
existing_record.write(bank_data)
elif original_amount:
self.env['property.bank.statements'].create(bank_data)
def property_bank_statements_ms_data(self, row):
if row[2].value:
start_time = datetime.strptime(row[2].value, '%Y-%m-%d %H:%M:%S')
else:
start_time = None
name = row[1].value
number = row[0].value
if row[3].value == "贷":
lending_direction = "loan"
elif row[3].value == "借":
lending_direction = "borrow"
name_account = row[4].value
bank_number = row[5].value
purpose = row[6].value
extract = row[7].value
remake = row[8].value
company_id = self.env['res.company'].browse(3)
pass
def property_bank_statements_zc_data(self, row, your_own_account, bank_branch_own):
"""广州银行(中城)解析:数据从第6行开始,列结构:
0:交易日期, 1:交易时间, 2:凭证种类, 3:凭证号,
4:对方账号, 5:对方户名, 6:借方金额, 7:贷方金额,
8:摘要, 9:余额, 10:备注(用途)
"""
# 交易日期+时间 拼成 datetime
date_val = str(row[0].value).strip()
time_val = str(row[1].value).strip()
if date_val and time_val and date_val != 'None':
try:
start_time = datetime.strptime(date_val + ' ' + time_val, '%Y%m%d %H:%M:%S')
except Exception:
start_time = None
else:
start_time = None
# 借贷方向与金额
borrow_str = str(row[6].value).replace(',', '').strip()
loan_str = str(row[7].value).replace(',', '').strip()
original_amount = 0.0
lending_direction = False
if borrow_str and borrow_str not in ('', '0', 'None', '-'):
try:
original_amount = float(borrow_str)
lending_direction = 'borrow'
except Exception:
pass
if loan_str and loan_str not in ('', '0', 'None', '-'):
try:
original_amount = float(loan_str)
lending_direction = 'loan'
except Exception:
pass
if not original_amount:
return
name = str(row[4].value).strip() # 对方账号
name_account = str(row[5].value).strip() # 对方户名
remake = str(row[8].value).strip() # 摘要
extract = str(row[10].value).strip() # 备注(用途)
your_own_unit = self.classify_by_last_four(your_own_account)
company_id = self._get_company_by_unit(your_own_unit)
if not company_id:
return
# 广州银行凭证号全为空,用 交易日期+时间+对方账号 组合去重
dedup_key = date_val + time_val + name
existing_record = self.env['property.bank.statements'].search(
[('serial_number', '=', dedup_key)], limit=1
)
bank_data = {
'name': name,
'name_account': name_account,
'serial_number': dedup_key,
'your_own_account': your_own_account,
'your_own_unit': your_own_unit,
'bank_branch': bank_branch_own,
'original_amount': original_amount,
'lending_direction': lending_direction,
'start_time': start_time - timedelta(hours=8) if start_time else False,
'extract': extract,
'remake': remake,
'company_id': company_id.id,
}
if not existing_record:
bank_data['bank_state'] = 'not_reconciled'
self.env['property.bank.statements'].create(bank_data)
else:
# 已存在记录,不修改 bank_state
existing_record.write(bank_data)
def property_bank_statements_dj_data(self, row, your_own_account):
"""农商行(东进)解析:数据从第4行开始,列结构:
0:序号, 1:电子回单号, 2:交易日期, 3:支出, 4:存入,
5:账户余额, 6:对方账号, 7:对方户名, 8:对方行名称,
9:摘要, 10:用途/其他摘要, 11:自然日期, 12:自然时间
"""
serial_number = str(row[1].value).strip()
if not serial_number or serial_number == 'None':
return
# 自然日期+自然时间 拼成 datetime
date_val = str(row[11].value).strip()
time_val = str(row[12].value).strip()
if date_val and time_val and date_val != 'None':
try:
start_time = datetime.strptime(date_val + ' ' + time_val, '%Y%m%d %H:%M:%S')
except Exception:
start_time = None
else:
start_time = None
# 支出/存入 取非0那个
borrow_str = str(row[3].value).replace(',', '').strip()
loan_str = str(row[4].value).replace(',', '').strip()
original_amount = 0.0
lending_direction = False
if borrow_str and borrow_str not in ('', '0', '0.0', 'None', '-'):
try:
val = float(borrow_str)
if val > 0:
original_amount = val
lending_direction = 'borrow'
except Exception:
pass
if loan_str and loan_str not in ('', '0', '0.0', 'None', '-'):
try:
val = float(loan_str)
if val > 0:
original_amount = val
lending_direction = 'loan'
except Exception:
pass
if not original_amount:
return
name = str(row[6].value).strip() # 对方账号
name_account = str(row[7].value).strip() # 对方户名
bank_branch = str(row[8].value).strip() # 对方行名称
remake = str(row[9].value).strip() # 摘要
extract = str(row[10].value).strip() # 用途
your_own_unit = self.classify_by_last_four(your_own_account)
company_id = self._get_company_by_unit(your_own_unit)
if not company_id:
return
existing_record = self.env['property.bank.statements'].search(
[('serial_number', '=', serial_number)], limit=1
)
bank_data = {
'name': name,
'name_account': name_account,
'serial_number': serial_number,
'your_own_account': your_own_account,
'your_own_unit': your_own_unit,
'bank_branch': bank_branch,
'original_amount': original_amount,
'lending_direction': lending_direction,
'start_time': start_time - timedelta(hours=8) if start_time else False,
'extract': extract,
'remake': remake,
'company_id': company_id.id,
}
if not existing_record:
bank_data['bank_state'] = 'not_reconciled'
self.env['property.bank.statements'].create(bank_data)
else:
# 已存在记录,不修改 bank_state
existing_record.write(bank_data)
def property_bank_statements_gc_data(self, row):
"""工商银行(兴源)解析:数据从第2行开始,列结构(共34列):
0:交易时间, 1:对方单位, 2:摘要, 4:转入金额(贷时取), 7:借贷标志,
8:发生额(借时取), 9:对方账号, 16:本方账号, 22:对方行名, 31:顺序号
金额取数规则:H列=贷 取E列(索引4转入金额),H列=借 取I列(索引8发生额)
"""
serial_number = str(row[31].value).strip() if row[31].value else ''
# 交易时间
time_val = str(row[0].value).strip() if row[0].value else ''
if time_val and time_val != 'None':
try:
start_time = datetime.strptime(time_val, '%Y-%m-%d %H:%M:%S')
except Exception:
start_time = None
else:
start_time = None
# 借贷方向,并按规则取对应金额列
direction_val = str(row[7].value).strip() if row[7].value else ''
if direction_val == '贷':
lending_direction = 'loan'
amount_str = str(row[4].value).replace(',', '').strip() if row[4].value else '' # E列:转入金额
elif direction_val == '借':
lending_direction = 'borrow'
amount_str = str(row[8].value).replace(',', '').strip() if row[8].value else '' # I列:发生额
else:
return
try:
original_amount = float(amount_str)
except Exception:
return
if not original_amount:
return
name = str(row[9].value).strip() if row[9].value else '' # 对方账号
name_account = str(row[1].value).strip() if row[1].value else '' # 对方单位
remake = str(row[2].value).strip() if row[2].value else '' # 摘要
bank_branch = str(row[22].value).strip() if row[22].value else '' # 对方行名
your_own_account = str(row[16].value).strip() if row[16].value else ''
your_own_unit = self.classify_by_last_four(your_own_account)
company_id = self._get_company_by_unit(your_own_unit)
if not company_id:
return
# 用顺序号去重,没有顺序号则用时间+对方账号
dedup_key = serial_number if serial_number and serial_number != '0' else (time_val + name)
existing_record = self.env['property.bank.statements'].search(
[('serial_number', '=', dedup_key)], limit=1
)
bank_data = {
'name': name,
'name_account': name_account,
'serial_number': dedup_key,
'your_own_account': your_own_account,
'your_own_unit': your_own_unit,
'bank_branch': bank_branch,
'original_amount': original_amount,
'lending_direction': lending_direction,
'start_time': start_time - timedelta(hours=8) if start_time else False,
'remake': remake,
'company_id': company_id.id,
'bank_state': 'not_reconciled',
}
if not existing_record:
self.env['property.bank.statements'].create(bank_data)
else:
existing_record.write(bank_data)
def property_bank_statements_xy_data_openpyxl(self, row):
"""工商银行(兴源)openpyxl版本解析,row为元组,列结构:
0:交易时间, 1:对方单位, 2:摘要, 4:转入金额(贷时取), 7:借贷标志,
8:发生额(借时取), 9:对方账号, 16:本方账号, 22:对方行名, 31:顺序号
"""
def get_val(idx):
v = row[idx] if len(row) > idx and row[idx] is not None else ''
return str(v).strip()
time_val = get_val(0)
if time_val and time_val != 'None':
try:
start_time = datetime.strptime(time_val, '%Y-%m-%d %H:%M:%S')
except Exception:
start_time = None
else:
start_time = None
direction_val = get_val(7)
if direction_val == '贷':
lending_direction = 'loan'
amount_str = get_val(4).replace(',', '') # E列:转入金额
elif direction_val == '借':
lending_direction = 'borrow'
amount_str = get_val(8).replace(',', '') # I列:发生额
else:
return
try:
original_amount = float(amount_str)
except Exception:
return
if not original_amount:
return
name = get_val(9) # 对方账号
name_account = get_val(1) # 对方单位
remake = get_val(2) # 摘要
bank_branch = get_val(22) # 对方行名
your_own_account = get_val(16)
serial_number = get_val(31) # 顺序号
your_own_unit = self.classify_by_last_four(your_own_account)
company_id = self._get_company_by_unit(your_own_unit)
if not company_id:
return
dedup_key = serial_number if serial_number and serial_number != '0' else (time_val + name)
existing_record = self.env['property.bank.statements'].search(
[('serial_number', '=', dedup_key)], limit=1
)
bank_data = {
'name': name,
'name_account': name_account,
'serial_number': dedup_key,
'your_own_account': your_own_account,
'your_own_unit': your_own_unit,
'bank_branch': bank_branch,
'original_amount': original_amount,
'lending_direction': lending_direction,
'start_time': start_time - timedelta(hours=8) if start_time else False,
'remake': remake,
'company_id': company_id.id,
}
if not existing_record:
bank_data['bank_state'] = 'not_reconciled'
self.env['property.bank.statements'].create(bank_data)
else:
# 已存在记录,不修改 bank_state
existing_record.write(bank_data)
def _get_company_by_unit(self, unit_name):
if unit_name == '天河物管':
return self.env['res.company'].sudo().browse(3)
elif unit_name == '天河粮储':
return self.env['res.company'].sudo().search([('name', '=', '天河粮储')], limit=1)
else:
return self.env['res.company'].sudo().search([('name', '=', '集团本部')], limit=1)
def classify_by_last_four(self, input_str):
if not isinstance(input_str, str) or not input_str.strip():
return ''
account = input_str.strip()
# 从物业公司配置表中查找匹配的收租公司账号
company_config = self.env['yuthon.property.company'].sudo().search(
[('rent_account', '=', account)], limit=1
)
if company_config:
return company_config
# 兼容旧逻辑:账号后四位模糊匹配
last_four = account[-4:] if len(account) >= 4 else ''
company_config = self.env['yuthon.property.company'].sudo().search(
[('rent_account', 'like', last_four)], limit=1
)
if company_config:
return company_config
return ''