Files
gzth/yuthon_property/models/property_meter_archives.py
T
2026-03-24 14:47:21 +08:00

468 lines
21 KiB
Python

from email.policy import default
from datetime import datetime, timedelta, date
from odoo import models, fields, api
from odoo.exceptions import UserError, ValidationError
from dateutil.relativedelta import relativedelta
class PropertyMeterArchives(models.Model):
_name = "property.meter.archives"
_description = "电表库"
_order = "number asc"
name = fields.Char(string="仪表编号")
unit_number = fields.Char(string="单元编号/用途")
number = fields.Integer(related="epitope_values_id.sequence", string="序号", store=True)
sequence1 = fields.Integer(string="序号")
installation_location = fields.Char(string="安装位置")
is_state = fields.Boolean(string="是否启用")
type = fields.Selection([("public", "公共"), ("fees", "收费")], string="类型")
gauge_magnification = fields.Integer(string="仪表倍率", default=1)
maximum_count = fields.Integer(string="最大位数")
lessee_id = fields.Many2one("property.tenant.information", string="承租方", store=True, compute="_compute_lessee_id",)
employee_ids = fields.Many2many("hr.employee", string="抄表人")
epitope_values = fields.Char(string="表位置")
epitope_values_id = fields.Many2one("yuthon.epitope.values", string="表位置")
other = fields.Char(string="其他")
unit = fields.Float(string="当月单价", digits=(16, 4))
archives_state = fields.Selection([("stop", "停用"), ("use", "正常"), ("rent", "出租")], string="状态", store=True, default="use",)
start_time = fields.Date(string="安装日期")
end_time = fields.Date(string="拆除日期")
standards_id = fields.Many2one("yuthon.electricity.setting", string="标准")
yuthon_property_ids = fields.Many2many("yuthon.property", string="物业信息")
line_ids = fields.One2many("meter.archives.line", "archives_id", string="明细行")
remarks = fields.Text(string="备注")
fixed_receivables = fields.Float(string="固定应收", digits=(16, 4))
is_virtual_meter = fields.Boolean(string="虚拟电表")
daily_readings = fields.Float(string="每日读数", digits=(16, 4))
moth_readings = fields.Float(string="每月读数", digits=(16, 4))
state = fields.Selection([("reading", "已抄"), ("unreading", "未抄")], string="抄表状态", default="unreading")
def update_ele_state(self):
current_date = fields.Date.today() + timedelta(hours=8)
records = self.env["property.meter.archives"].search([])
for record in records:
if record.end_time:
if current_date >= record.end_time:
record.archives_state = "stop"
def delete_data(self):
archives_ids = self.env["property.meter.archives"].browse(
self._context.get('active_ids', self._context.get('active_id')))
for archive in archives_ids:
if archive.line_ids:
lines_to_delete = archive.line_ids.filtered(
lambda l:
l.meter_date # 确保日期非空
and l.meter_date.year == 2025 # 固定2025年
and l.meter_date.month in [10, 11] # 10/11月
)
if lines_to_delete:
lines_to_delete.unlink()
def meter_tasks(self):
current_date = fields.Date.today()
current_date = current_date.replace(day=15)
month_start = current_date.replace(day=1)
if current_date.month == 12:
month_end = current_date.replace(year=current_date.year + 1, month=1, day=1) - timedelta(days=1)
else:
month_end = current_date.replace(month=current_date.month + 1, day=1) - timedelta(days=1)
meter_archives = self.search([])
for archive in meter_archives:
existing_lines = self.env["meter.archives.line"].search([
("archives_id", "=", archive.id),
("meter_date", ">=", month_start),
("meter_date", "<=", month_end),
], limit=1)
if not existing_lines:
self.env["meter.archives.line"].create({
"archives_id": archive.id,
"epitope_values_id": archive.epitope_values_id.id,
"meter_date": current_date,
"state": "unreading",
"archives_state": "use",
"unit": archive.unit or 0.0,
"gauge_magnification": archive.gauge_magnification or 1,
"line_loss": 0.02,
"type": archive.type,
"contract_id": archive.line_ids[-1].contract_id.id if archive.line_ids else None,
"unit_number": archive.unit_number or "",
"maximum_count": archive.maximum_count or 0,
})
def change_floor_address(self):
archives_ids = self.env["property.meter.archives"].browse(self._context.get("active_ids", self._context.get("active_id")))
for meter_id in archives_ids:
meter_id._constrains_property_energy()
@api.onchange("moth_readings")
def _onchange_moth_readings(self):
if self.moth_readings:
self.daily_readings = 0
@api.onchange("daily_readings")
def _onchange_daily_readings(self):
if self.daily_readings:
self.moth_readings = 0
# @api.model_create_multi 多条创建
@api.model
def create(self, vals):
record = super().create(vals)
if "name" in vals and vals["name"]:
existing_records = self.search([("name", "=", vals["name"]), ("id", "!=", record.id)])
if existing_records:
raise ValidationError(f"仪表编号 '{vals['name']}' 已存在,请使用其他编号!")
return record
@api.depends("yuthon_property_ids.lessee_id")
def _compute_lessee_id(self):
for record in self:
if record.yuthon_property_ids:
record.lessee_id = record.yuthon_property_ids[0].lessee_id
else:
record.lessee_id = False
@api.constrains("yuthon_property_ids", "epitope_values_id", "maximum_count")
def _constrains_property_energy(self):
for record in self:
selected_properties = record.yuthon_property_ids
# 1. 当没有选中物业时,删除所有关联的明细行
if not selected_properties:
meters_to_remove = self.env['yuthon.property.energy'].search([
('property_meter_archives_id', '=', record.id),
])
if meters_to_remove:
related_properties = meters_to_remove.mapped('yuthon_property_id')
meters_to_remove.unlink()
# 更新物业的电表标记
for prop in related_properties:
prop.is_electricity_meter = bool(prop.energy_meter_ids)
# 删除不在选中列表中的物业对应的明细行
all_meters = self.env['yuthon.property.energy'].search([
('property_meter_archives_id', '=', record.id),
])
meters_to_remove = all_meters.filtered(
lambda m: m.yuthon_property_id not in selected_properties
)
if meters_to_remove:
related_properties = meters_to_remove.mapped('yuthon_property_id')
meters_to_remove.unlink()
for prop in related_properties:
prop.is_electricity_meter = bool(prop.energy_meter_ids)
# 为新增的选中物业创建明细行
for prop in selected_properties:
existing_meter = self.env['yuthon.property.energy'].search([
('property_meter_archives_id', '=', record.id),
('yuthon_property_id', '=', prop.id)
], limit=1)
if not existing_meter:
self.env['yuthon.property.energy'].create({
'property_meter_archives_id': record.id,
'yuthon_property_id': prop.id,
})
# 更新物业的电表标记
prop.is_electricity_meter = bool(prop.energy_meter_ids)
if record.line_ids:
write_vals = {
'epitope_values_id': record.epitope_values_id.id,
'maximum_count': record.maximum_count,
'yuthon_property_ids': [(6, 0, record.yuthon_property_ids.ids)]
if record.yuthon_property_ids else [(5,)]
}
record.line_ids.write(write_vals)
def open_record(self):
return {
"type": "ir.actions.act_window",
"res_model": "property.meter.archives",
"res_id": self.id,
"view_mode": "form",
}
def button_archives_state(self):
archives_ids = self.env["property.meter.archives"].browse(
self._context.get("active_ids", self._context.get("active_id"))
)
for record in archives_ids:
record.archives_state = "stop"
def update_property(self):
reading_ids = self.env["property.meter.archives"].browse(
self._context.get("active_ids", self._context.get("active_id"))
)
for record in reading_ids:
record.onchange_property()
def action_property_meter_wizard(self):
return {
"type": "ir.actions.act_window",
"name": "批量导入物业电表",
"res_model": "property.meter.wizard",
"view_mode": "form",
"target": "new",
}
def reading_publish(self):
menu_id = self.env["ir.ui.menu"].search([("name", "=", "水表抄表")])
reading_ids = self.env["property.meter.archives"].browse(
self._context.get("active_ids", self._context.get("active_id"))
)
for record in reading_ids:
for employee_id in record.employee_ids:
url = f"https://oa.thtzjt.com/web#id={self.id}&cids=2&action={menu_id.action.id}&model=property.meter.archives&view_type=list&menu_id={menu_id.id}"
we_id = (
self.env["hr.employee"]
.search([("id", "=", employee_id)])
.wecom_userid
)
self.sudo().env["wecom.apps"].sync_send_message_textcard(
category="notice",
user_id=we_id,
title="通知公告",
description="您有抄表待处理,请及时处理",
url=url,
btntxt="详细信息",
)
def batch_reading_push(self):
active_ids = self._context.get("active_ids", [])
return {
"name": "电水表推送",
"type": "ir.actions.act_window",
"res_model": "electric.water.wizard",
"view_mode": "form",
"view_type": "form",
"target": "new",
"context": {"default_meter_ids": [(6, 0, active_ids)]},
}
class MeterArchivesLine(models.Model):
_name = "meter.archives.line"
_description = "电表库明细行"
_order = "number asc, sequence asc, last_meter asc"
is_acc = fields.Boolean(string="新租", default=False)
archives_id = fields.Many2one("property.meter.archives", string="电表库", required=True, ondelete="cascade")
# name = fields.Char(string='仪表编号')
number = fields.Integer(related="epitope_values_id.sequence", string="序号", store=True)
sequence = fields.Integer(related="archives_id.sequence1", string="表序号", store=True)
epitope_values_id = fields.Many2one("yuthon.epitope.values", string="表位置")
lessee_id = fields.Many2one("property.tenant.information", string="承租方", store=True)
yuthon_property_ids = fields.Many2many("yuthon.property", string="物业信息")
last_meter = fields.Date(string="上次抄表日期", compute="_compute_last", store=True)
last_dosage = fields.Float(string="上次用量", store=True, group_operator=None, compute="_compute_last")
initial_value = fields.Float(string="初始读数")
last_read = fields.Float(string="上次读数", store=True, group_operator=None, compute="_compute_last")
meter_date = fields.Date(string="任务日期")
meter_date_month = fields.Char(string="任务日期月份", index=True, copy=False)
current_meter = fields.Float(string="本次用量", compute="_compute_current_meter")
is_abnormal = fields.Boolean(string="是否异常", compute="_compute_is_abnormal", store=True)
current_read = fields.Float(string="本次读数", group_operator=None)
unit_number = fields.Char(string="单元编号/用途")
gauge_magnification = fields.Integer(string="仪表倍率", default=1, group_operator=None)
last_reading_time = fields.Date(string="最后读数时间")
receivables = fields.Float(string="应收", store=True, help="应收金额=本次用量*单价*(1+线损)")
unit = fields.Float(string="单价", group_operator=None, digits=(16, 4))
line_loss = fields.Float(string="线损", default=0.02)
type = fields.Selection([("public", "公共"), ("fees", "收费")], string="类型")
employee_ids = fields.Many2many("hr.employee", string="抄表人")
remarks = fields.Text(string="备注")
is_fix_loss = fields.Selection([('yes', '是'), ('no', '否')], default="no", string="是否固定线损")
is_fix_price = fields.Selection([('yes', '是'), ('no', '否')], default="no", string="是否固定单价")
archives_state = fields.Selection([("stop", "停用"), ("use", "正常"), ("rent", "出租")], string="状态", default="use",)
state = fields.Selection([("reading", "已抄"), ("unreading", "未抄")], string="抄表状态", default="unreading",)
maximum_count = fields.Integer(string="最大位数")
is_push = fields.Boolean(string='推送', default=False)
def delete_dec_2025_meter_data(self):
dec_2025_start = datetime(2025, 12, 1).date()
dec_2025_end = datetime(2025, 12, 31).date()
dec_2025_records = self.search([
("meter_date", ">=", dec_2025_start),
("meter_date", "<=", dec_2025_end),
])
dec_2025_records.unlink()
@api.depends('current_read', 'last_read')
def _compute_is_abnormal(self):
"""
计算是否异常:本次读数与上次读数差值超过50%则为True
"""
for rec in self:
rec.is_abnormal = False
if not rec.last_read or not rec.current_read:
continue
diff = abs(rec.current_read - rec.last_read)
diff_ratio = diff / rec.last_read
if diff_ratio > 0.5:
rec.is_abnormal = True
def update_is_abnormal(self):
archives_ids = self.env["meter.archives.line"].browse(
self._context.get('active_ids', self._context.get('active_id')))
for ins in archives_ids:
ins._compute_is_abnormal()
@api.model_create_multi
def create(self, vals_list):
records = super().create(vals_list)
for rec in records:
if rec.meter_date:
rec.meter_date_month = rec.meter_date.strftime("%Y-%m")
else:
rec.meter_date_month = fields.Date.today().strftime("%Y-%m")
rec.archives_id._constrains_property_energy()
return records
@api.depends('last_read', 'current_read')
def _compute_current_meter(self):
for record in self:
if record.current_read >= record.last_read:
record.current_meter = record.current_read - record.last_read
else:
if record.current_read:
if record.maximum_count > 0:
max_value = 10 ** record.maximum_count - 1
record.current_meter = max_value - record.last_read + record.current_read
else:
record.current_meter = 0
else:
record.current_meter = 0
@api.onchange('current_meter', 'unit', 'line_loss')
def onchange_receivables(self):
for record in self:
# 因为current_meter 取不到值所有复制一遍
if record.current_read >= record.last_read:
current_meter = record.current_read - record.last_read
else:
if record.current_read:
if record.maximum_count > 0:
max_value = 10 ** record.maximum_count - 1
current_meter = max_value - record.last_read + record.current_read
else:
current_meter = 0
else:
current_meter = 0
if current_meter and record.unit:
raw_value = current_meter * record.unit * (1 + record.line_loss) * record.gauge_magnification
record.receivables = round(raw_value, 1)
else:
record.receivables = 0.0
def update_date(self):
archives_ids = self.env["meter.archives.line"].browse(
self._context.get('active_ids', self._context.get('active_id')))
for record in archives_ids:
record.onchange_lessee_id()
def update_meter(self):
current_year = datetime.now().year
target_date = datetime(current_year, 9, 1).date()
domain = [
("state", "=", "unreading"), # 只处理未抄的记录
("meter_date", "<", target_date), # 任务日期在9月之前
]
active_ids = self._context.get("active_ids", self._context.get("active_id"))
if active_ids:
domain.append(("id", "in", active_ids))
unread_meter_lines = self.env["meter.archives.line"].search(domain)
if unread_meter_lines:
unread_meter_lines.write({"state": "reading"})
@api.depends("archives_id", "last_dosage", "last_read", "last_meter", "current_meter", "current_read","maximum_count",)
def _compute_last(self):
"""
上次用量,读数,超标日期
:return:
"""
for i in self:
last_id = (
self.env["meter.archives.line"]
.search(
[
("meter_date", "<", i.meter_date),
("archives_id", "=", i.archives_id.id),
]
)
.sorted("meter_date")
)
if last_id:
i.last_dosage = last_id[-1].current_meter
i.last_read = last_id[-1].current_read
i.last_meter = last_id[-1].meter_date
if i.current_read >= i.last_read:
i.current_meter = i.current_read - i.last_read
else:
if i.current_read != 0:
if i.maximum_count > 0:
max_value = 10**i.maximum_count - 1
i.current_meter = max_value - i.last_read + i.current_read
else:
i.current_meter = 0
else:
i.current_meter = 0
else:
i.last_dosage = 0
i.last_read = i.initial_value
i.last_meter = i.meter_date - relativedelta(months=1)
if i.current_read != 0:
if i.maximum_count > 0:
max_value = 10 ** i.maximum_count - 1
i.current_meter = max_value - i.last_read + i.current_read
else:
i.current_meter = 0
else:
i.current_meter = 0
@api.onchange('current_read', 'last_read', 'maximum_count')
def onchange_current_meter(self):
for record in self:
if record.last_read is not None and record.current_read is not None and record.last_read > 0:
if record.current_read < record.last_read:
if record.current_meter>= 10000:
raise ValidationError(
f"输入错误:仪表编号为{record.archives_id.name}当前读数 {record.current_read} 必须大于上次读数 {record.last_read} "
)
@api.onchange("yuthon_property_ids")
def onchange_lessee_id(self):
for record in self:
lessee = False
if record.yuthon_property_ids:
for prop in record.yuthon_property_ids:
if prop.lessee_id:
lessee = prop.lessee_id
break
record.lessee_id = lessee
def push_task_push_wizard(self):
meter_ids = self.env["meter.archives.line"].browse(
self._context.get("active_ids", self._context.get("active_id"))
)
for meter_id in meter_ids:
if meter_id.is_push == True:
raise UserError(f"电表名为{meter_id.archives_id}任务已推送,请勿重复推送")
return {
"name": "电水表任务",
"type": "ir.actions.act_window",
"res_model": "task.push.wizard",
"view_mode": "form",
"target": "new",
"context": {
"default_meter_ids": meter_ids.ids,
}
}