Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
pengyuthon
2026-03-18 14:38:56 +08:00
30 changed files with 891 additions and 122 deletions
+1
View File
@@ -14,6 +14,7 @@
'report/hr_leave_batch_report_action.xml',
'views/inherit_hr_type_views.xml',
'views/inherit_hr_leave_allocation_views.xml',
'views/extended_vacation_approval_views.xml',
'views/inherit_hr_leave_accrual_plan_views.xml',
'views/hr_leave_accrual_views.xml',
'views/yuthon_fixes_approval_views.xml',
@@ -14,6 +14,13 @@
<field name="padding">4</field>
<field name="company_id" eval="False"/>
</record>
<record id="yuthon_extended_sequence" model="ir.sequence">
<field name="name">修正编号</field>
<field name="code">yuthon_extended_code</field>
<field name="prefix">JQYC</field>
<field name="padding">4</field>
<field name="company_id" eval="False"/>
</record>
<record id="yuthon_breastfeed_leave_sequence" model="ir.sequence">
<field name="name">哺乳编号</field>
<field name="code">yuthon_breastfeed_leave_code</field>
+2 -1
View File
@@ -4,4 +4,5 @@ from . import inherit_hr_leave
from . import inherit_hr_leave_accrual_plan
from . import yuthon_fixes_approval
from . import yuthon_holiday_setting
from . import yuthon_breastfeed_leave
from . import yuthon_breastfeed_leave
from . import extended_vacation_approval
@@ -0,0 +1,90 @@
# -*- coding: utf-8 -*-
from email.policy import default
from odoo import models, fields, api
class ExtendedVacationApproval(models.Model):
_name = 'extended.vacation.approval'
_description = '假期延长申请'
_inherit = ['soong.workflow.mixin', 'mail.thread.main.attachment', 'mail.activity.mixin']
name = fields.Char(string="标题")
code = fields.Char(string="编号", default=lambda self: self.env['ir.sequence'].next_by_code('yuthon_extended_code'))
users_id = fields.Many2one('res.users', string="申请人", default=lambda self: self.env.user.id)
department_id = fields.Many2one(related='users_id.employee_id.department_id', string="部门")
employee_ids = fields.Many2many('hr.employee', string="假期顺延人员")
type_vacations_ids = fields.Many2many('hr.leave.type', string="假期类型")
is_issues = fields.Boolean(string='是否上会')
issues_result = fields.Html(string="会议结果")
related_issues_id = fields.Many2one('issue.library', string="关联议题")
reason_application = fields.Text(string="申请事由")
documents_ids = fields.Many2many('ir.attachment', string="附件")
date_postponed = fields.Date(string="延期至")
active = fields.Boolean('Active', default=True, tracking=True)
remarks = fields.Text(string="备注")
work_end = fields.Boolean(string="结束")
urgency_level = fields.Selection([('normal', '普件'), ('urgent', '急件'), ('emergency', '特急件')], string='紧急度',
default='normal')
line_ids = fields.One2many('extended.vacation.line', 'vacation_id', string="延期假期")
def _get_default_name_title(self):
return self._description if self._description else self._name
name_title = fields.Char(default=_get_default_name_title)
self_users_ids = fields.Many2many('res.users', string="当前经办人", default=lambda self: [(6, 0, [self.env.user.id])])
@api.onchange('employee_ids')
def _onchange_employee_ids(self):
if not self.employee_ids:
self.line_ids = [(5, 0, 0)]
return
current_date = fields.Date.today()
target_date = fields.Date.to_date(f"{current_date.year}-{current_date.month}-01")
self.line_ids = [(5, 0, 0)]
line_values = []
for employee in self.employee_ids:
report_record = self.env['yuthon.vacation.reports'].search([
('employee_id', '=', employee.id.origin),
('vacation_date', '=', target_date)
], limit=1)
line_data = {
'employee_id': employee.id,
'dynamic_previous_year': report_record.dynamic_previous_year if report_record else 0.0,
'dynamic_previous_year1': report_record.dynamic_previous_year1 if report_record else 0.0,
}
line_values.append((0, 0, line_data))
self.line_ids = line_values
def end_approval(self):
current_date = fields.Date.today()
start_date = fields.Date.to_date(f"{current_date.year}-01-01")
end_date = fields.Date.to_date(f"{current_date.year}-{current_date.month}-01")
if self.employee_ids:
has_annual_leave = self.type_vacations_ids.filtered(lambda r: r.name == '年假')
has_overtime_leave = self.type_vacations_ids.filtered(lambda r: r.name == '补休')
for employee in self.employee_ids:
report_records = self.env['yuthon.vacation.reports'].search([
('employee_id', '=', employee.id),
('vacation_date', '>=', start_date),
('vacation_date', '<=', end_date)
])
if report_records:
write_vals = {}
if has_annual_leave:
write_vals['valid_date'] = self.date_postponed
if has_overtime_leave:
write_vals['valid_date1'] = self.date_postponed
if write_vals:
report_records.write(write_vals)
for record in report_records:
record.update_line()
class ExtendedVacationLine(models.Model):
_name = 'extended.vacation.line'
_description = "假期延长明细"
vacation_id = fields.Many2one('extended.vacation.approval', string="假期延长")
employee_id = fields.Many2one('hr.employee', string="姓名")
dynamic_previous_year = fields.Float(string="上年年假剩余d", digits=(16, 3))
dynamic_previous_year1 = fields.Float(string="上年补休剩余d", digits=(16, 3))
+3 -1
View File
@@ -6,4 +6,6 @@ access_yuthon_fixes_wizard,yuthon_fixes_wizard,model_yuthon_fixes_wizard,base.gr
access_yuthon_holiday_setting,yuthon_holiday_setting,model_yuthon_holiday_setting,base.group_user,1,1,1,1
access_leave_allocation_wizard,leave_allocation_wizard,model_leave_allocation_wizard,base.group_user,1,1,1,1
access_yuthon_leave_wizard,yuthon_leave_wizard,model_yuthon_leave_wizard,base.group_user,1,1,1,1
access_yuthon_breastfeed_leave,yuthon_breastfeed_leave,model_yuthon_breastfeed_leave,base.group_user,1,1,1,1
access_yuthon_breastfeed_leave,yuthon_breastfeed_leave,model_yuthon_breastfeed_leave,base.group_user,1,1,1,1
access_extended_vacation_approval,extended_vacation_approval,model_extended_vacation_approval,base.group_user,1,1,1,1
access_extended_vacation_line,extended_vacation_line,model_extended_vacation_line,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
6 access_yuthon_holiday_setting yuthon_holiday_setting model_yuthon_holiday_setting base.group_user 1 1 1 1
7 access_leave_allocation_wizard leave_allocation_wizard model_leave_allocation_wizard base.group_user 1 1 1 1
8 access_yuthon_leave_wizard yuthon_leave_wizard model_yuthon_leave_wizard base.group_user 1 1 1 1
9 access_yuthon_breastfeed_leave yuthon_breastfeed_leave model_yuthon_breastfeed_leave base.group_user 1 1 1 1
10 access_extended_vacation_approval extended_vacation_approval model_extended_vacation_approval base.group_user 1 1 1 1
11 access_extended_vacation_line extended_vacation_line model_extended_vacation_line base.group_user 1 1 1 1
@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="view_extended_vacation_approval_tree" model="ir.ui.view">
<field name="name">extended.vacation.approval.tree</field>
<field name="model">extended.vacation.approval</field>
<field name="arch" type="xml">
<tree string="假期延长申请">
<field name="users_id"/>
<field name="department_id"/>
</tree>
</field>
</record>
<record id="view_extended_vacation_approval_form" model="ir.ui.view">
<field name="name">extended.vacation.approval.form</field>
<field name="model">extended.vacation.approval</field>
<field name="arch" type="xml">
<form string='假期延长审批' create="1">
<sheet>
<widget name="web_ribbon" title="普件" bg_color="text-bg-success"
invisible="urgency_level != 'normal'"/>
<widget name="web_ribbon" title="急件" bg_color="text-bg-warning"
invisible="urgency_level != 'urgent'"/>
<widget name="web_ribbon" title="特急" bg_color="text-bg-danger"
invisible="urgency_level != 'emergency'"/>
<group col="4">
<group>
<field name="code" string="流水号" readonly="1"/>
</group>
<group>
<field name="urgency_level" readonly="1"/>
</group>
<group>
<field name="is_issues"/>
</group>
<group>
<field name="related_issues_id" invisible="is_issues == False"/>
</group>
</group>
<div class="col-12 text-center">
<h1>
<field name="name_title" readonly="1"/>
</h1>
</div>
<group string="审批事项">
<group>
<field name="users_id"/>
<field name="type_vacations_ids" widget="many2many_tags"/>
<field name="department_id" invisible="1"/>
<field name="date_postponed"/>
<field name="employee_ids" widget="many2many_tags"/>
</group>
</group>
<group string="延长明细">
<field name="line_ids" string=" ">
<tree editable="bottom">
<field name="vacation_id" column_invisible="1"/>
<field name="employee_id"/>
<field name="dynamic_previous_year"/>
<field name="dynamic_previous_year1"/>
</tree>
</field>
</group>
<group>
<field name="reason_application"/>
<field name="work_end" invisible="1"/>
<field name="documents_ids" widget="preview_many2many"/>
</group>
</sheet>
</form>
</field>
</record>
</odoo>
@@ -377,17 +377,33 @@ class YuthonVacationReports(models.Model):
rec.year_workday += workday_overtime_add
rec.year_holiday += holidays_overtime_add
if rec.working_overtime > 0:
current_month_first = rec.vacation_date.replace(day=1)
last_month_last = current_month_first - timedelta(days=1)
last_month_first = last_month_last.replace(day=1)
last_month_record = self.env['yuthon.vacation.reports'].search([
('employee_id', '=', rec.employee_id.id),
('vacation_date', '=', last_month_first),
], limit=1)
if last_month_record:
total_overtime = last_month_record.dynamic_previous_year1 + last_month_record.this_year1
else:
total_overtime = 0.0
if total_overtime > 0:
working_overtime_lines = rec.overtime_ids.filtered(
lambda o: not o.workflow_record_id and o.text == '上月额度继承'
)
working_overtime_lines.workday = rec.working_overtime * 8
working_overtime_lines.workday = total_overtime * 8
if last_month_record:
total_leave = last_month_record.dynamic_previous_year + last_month_record.dynamic_remainder_year
else:
total_leave = 0.0
if rec.leave > 0:
if total_leave > 0:
leave_lines = rec.leave_ids.filtered(
lambda o: not o.workflow_record_id and o.text == '上月额度继承'
)
leave_lines.update_lines = rec.leave * 8
leave_lines.update_lines = total_leave * 8
rec._compute_leave()
rec._compute_working_overtime()
@@ -1365,12 +1381,21 @@ class YuthonVacationReports(models.Model):
}
rec.write(inherit_vals)
current_date = fields.Date.today()
if last_month_record:
if last_month_record.valid_date and last_month_record.valid_date < current_date:
dynamic_previous_year_val = 0.0
else:
dynamic_previous_year_val = last_month_record.dynamic_previous_year
if last_month_record.valid_date1 and last_month_record.valid_date1 < current_date:
dynamic_previous_year1_val = 0.0
else:
dynamic_previous_year1_val = last_month_record.dynamic_previous_year1
inherit_vals = {
'leave': last_month_record.leave,
'dynamic_previous_year': last_month_record.dynamic_previous_year,
'dynamic_previous_year': dynamic_previous_year_val,
'dynamic_remainder_year': last_month_record.dynamic_remainder_year,
'dynamic_previous_year1': last_month_record.dynamic_previous_year1,
'dynamic_previous_year1': dynamic_previous_year1_val,
'this_year1': last_month_record.this_year1,
'working_overtime': last_month_record.working_overtime,
'medical_fee': last_month_record.medical_fee,
@@ -195,11 +195,14 @@ class ReceivableModifyLine(models.Model):
bank_id = fields.Many2one('property.bank.statements', string='关联银行对账单')
original_amount = fields.Float(related='bank_id.original_amount', string='交易金额', store=False)
no_amount = fields.Float(related='bank_id.no_amount', string='未对', store=False)
bank_state = fields.Selection(related='bank_id.bank_state', string='状态', store=False)
receivable_id = fields.Many2one('property.accounts.receivable', string='应收账单')
management_type_id = fields.Many2one('yuthon.management.type', related='receivable_id.management_type_id', string='应收类型', store=False)
due_date = fields.Date(related='receivable_id.due_date', string='应收开始', store=False)
due_date_end = fields.Date(related='receivable_id.due_date_end', string='应收结束', store=False)
property_name = fields.Text(string='物业', compute='_compute_property_name', store=False, search='_search_property_name')
delivered1 = fields.Float(related='receivable_id.delivered1', string='已收金额', store=False)
is_write = fields.Boolean()
@api.depends('receivable_id')
@@ -225,6 +228,32 @@ class ReceivableModifyLine(models.Model):
not_collected = fields.Float(string='未收')
amount = fields.Float(string='已对金额')
def open_receivable(self):
"""打开应收账单"""
self.ensure_one()
if self.receivable_id:
return {
'type': 'ir.actions.act_window',
'name': '应收账单',
'res_model': 'property.accounts.receivable',
'res_id': self.receivable_id.id,
'view_mode': 'form',
'target': 'current',
}
def open_bank_statement(self):
"""打开银行流水"""
self.ensure_one()
if self.bank_id:
return {
'type': 'ir.actions.act_window',
'name': '银行流水',
'res_model': 'property.bank.statements',
'res_id': self.bank_id.id,
'view_mode': 'form',
'target': 'current',
}
@@ -116,16 +116,21 @@
<field name="arch" type="xml">
<tree string="应收流水中间表" editable="top">
<field name="bank_id"/>
<field name="no_amount"/>
<field name="bank_state"/>
<field name="reconciliation_date"/>
<field name="not_collected"/>
<field name="amount"/>
<field name="original_amount"/>
<field name="delivered1"/>
<field name="receivable_id"/>
<field name="property_name"/>
<field name="due_date"/>
<field name="due_date_end"/>
<field name="management_type_id"/>
<field name="is_penalty"/>
<button name="open_receivable" string="应收账单" type="object" class="btn btn-blue"/>
<button name="open_bank_statement" string="银行流水" type="object" class="btn btn-blue"/>
</tree>
</field>
</record>
@@ -66,7 +66,7 @@
<field name="epitope_values_id" domain="[('type', '=', 'ammeter')]" options="{'no_open': True}" column_invisible="1"/>
<field name="meter_date"/>
<field name="last_read" readonly="1" />
<field name="current_read" />
<field name="current_read" decoration-warning="1"/>
<field name="last_dosage" />
<field name="current_meter" readonly="1" />
<field name="unit" />
@@ -110,7 +110,7 @@
<field name="gauge_magnification" string="倍率"/>
<field name="current_meter" string="用量"/>
<field name="initial_value"/>
<field name="current_read" string="本次读数"/>
<field name="current_read" string="本次读数" decoration-warning="1"/>
<field name="last_read" string="上次读数"/>
<field name="lessee_id" string="承租方"/>
<field name="yuthon_property_ids" string="地址" widget="many2many_tags"/>
@@ -66,7 +66,7 @@
domain="[('type', '=', 'watermeter')]" options="{'no_create': True, 'no_open': True}"/>
<field name="meter_date"/>
<field name="last_read" readonly="1" />
<field name="current_read" />
<field name="current_read" decoration-warning="1"/>
<field name="last_dosage" />
<field name="current_meter" readonly="1" />
<field name="unit" />
@@ -112,7 +112,7 @@
<field name="line_loss" string="线损"/>
<field name="gauge_magnification" string="倍率"/>
<field name="current_meter" string="用量"/>
<field name="current_read" string="本次读数"/>
<field name="current_read" string="本次读数" decoration-warning="1"/>
<field name="last_read" string="上次读数"/>
<field name="lessee_id" string="承租方"/>
<field name="yuthon_property_ids" string="地址" widget="many2many_tags"/>
@@ -11,7 +11,7 @@ class PropertyStatementsWizard(models.Model):
_name = 'property.statements.wizard'
_description = '银行流水向导弹窗'
selection_bank = fields.Selection([('js', '建设银行'), ('gs', '工商银行'),('sh', '农商')], string="选择银行")
selection_bank = fields.Selection([('js', '建设银行'), ('gs', '工商银行'),('sh', '农商'), ('ms', '民生'), ('zc', '中城'), ('dj', '东进')], string="选择银行")
excel_file = fields.Binary(string="上传Excel表")
excel_name = fields.Char(string="文件名")
@@ -31,6 +31,12 @@ class PropertyStatementsWizard(models.Model):
self.property_bank_statements_gc_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)
elif self.selection_bank == 'zc':
self.property_bank_statements_zc_data(row)
elif self.selection_bank == 'dj':
self.property_bank_statements_dj_data(row)
def property_bank_statements_gc_data(self, row):
if row[0].value:
@@ -171,6 +177,31 @@ class PropertyStatementsWizard(models.Model):
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'].search([('name', '=', '天河物管')], limit=1)
pass
def property_bank_statements_zc_data(self, row):
pass
def property_bank_statements_dj_data(self, row):
pass
def classify_by_last_four(self, input_str):
if not isinstance(input_str, str) or len(input_str) < 4:
@@ -201,6 +201,7 @@ class PropertyBankStatements(models.Model):
@api.depends('name')
def _compute_property_employee_id(self):
for record in self:
# 1. 原有逻辑:根据银行名称匹配承租方
if record.name:
bank_account = self.env['property.bank.line'].search([
('bank', '=', record.name)
@@ -209,20 +210,38 @@ class PropertyBankStatements(models.Model):
record.lessee_ids = [(6, 0, bank_list)] if bank_list else [(6, 0, [])]
else:
record.lessee_ids = [(6, 0, [])]
# 2. 重构核心逻辑:按日期筛选出租方变更行,匹配所属单元
if record.lessee_ids:
contract_set = self.env['property.lease.contract']
# 获取当前记录的日期(用于判断出租方变更行的生效期)
target_date = record.start_time.date() if record.start_time else fields.Date.today()
for lessee in record.lessee_ids:
# 从承租方的 property_contract_ids(租户行)提取合同
# 从承租方的租户行提取合同
tenant_lines = lessee.property_contract_ids.filtered(
lambda line:
line.lease_contract_id and
line.lease_contract_id.lessor_id and
line.lease_contract_id.lessor_id.referred == record.your_own_unit
lambda line: line.lease_contract_id is not False
)
contract_ids = tenant_lines.mapped('lease_contract_id.id')
contracts = self.env['property.lease.contract'].browse(contract_ids)
contracts = contracts.filtered(lambda c: c.contact_state != 'settle')
contract_set |= contracts
for tenant_line in tenant_lines:
contract = tenant_line.lease_contract_id
if not contract:
continue
# 1. 筛选合同的出租方变更行:生效日期≤目标日期,失效日期≥目标日期/未设置
valid_lessor_lines = contract.lessor_line.filtered(
lambda l:
l.start_date <= target_date and
(not l.end_date or l.end_date >= target_date)
)
# 2. 确定最终的出租方referred值
if valid_lessor_lines:
actual_lessor_referred = valid_lessor_lines[0].lessor_id.referred
else:
actual_lessor_referred = contract.lessor_id.referred if contract.lessor_id else False
if actual_lessor_referred == record.your_own_unit:
if contract.contact_state != 'settle':
contract_set |= contract
record.contract_ids = contract_set
record.admin_id = record.contract_ids[0].admin_id if record.contract_ids else False
else:
+3
View File
@@ -16,6 +16,8 @@
'views/penalty_month_received_report.xml',
'views/penalty_year_cum_report.xml',
'views/deposit_reports.xml',
'views/cum_income_report.xml',
'views/cum_arrears_report.xml',
'views/menu.xml',
'views/ledger_report_views.xml',
'views/receivable_report_views.xml',
@@ -32,6 +34,7 @@
'views/yuthon_report_res_partner.xml',
'views/att_report_view.xml',
'views/staff_position_report.xml',
'views/hydropower_report_views.xml',
],
'installable': True,
'application': True,
+4 -1
View File
@@ -18,6 +18,8 @@ from . import pre_arrears_report
from . import pre_advance_report
from . import month_receivable_report
from . import total_received_report
from . import cum_income_report
from . import cum_arrears_report
from . import penalty_arrears_report
from . import penalty_month_received_report
from . import penalty_year_cum_report
@@ -25,4 +27,5 @@ from . import deposit_month_receivable_report
from . import deposit_pre_received_report
from . import deposit_month_received_report
from . import deposit_cum_arrears_report
from . import deposit_month_refund_report
from . import deposit_month_refund_report
from . import hydropower_report
+112
View File
@@ -0,0 +1,112 @@
import datetime
import calendar
from odoo import api, models
class CumArrearsReport(models.Model):
_inherit = 'receivable.modify.line'
@api.model
def get_cum_arrears_report(self, params):
"""
累计欠收报表(数据源:receivable.modify.line):
- 状态为挂账(bank_state = 'registered')
- 应收开始日期(receivable_id.due_date)小于等于查询月月末
- 排除押金和违约金类型
- 金额取 not_collected(未收)
"""
today = datetime.date.today()
date_start = params.get("date_start")
if date_start:
query_date = datetime.datetime.fromisoformat(date_start).date()
else:
query_date = today
# 查询月月末
last_day = calendar.monthrange(query_date.year, query_date.month)[1]
month_end = query_date.replace(day=last_day)
domain = [
('bank_state', '=', 'registered'),
('receivable_id.due_date', '<=', month_end),
('receivable_id.is_invalid', '=', False),
]
keyword = params.get("keyword")
if keyword:
domain += [
'|', '|',
('receivable_id.contract_id.contract_code', 'ilike', keyword),
('receivable_id.property_name', 'ilike', keyword),
('receivable_id.lessee_id.name', 'ilike', keyword),
]
condition = params.get('condition')
partner_id = params.get('partner_id')
company_id = params.get('company_id')
management_type_id = params.get('management_type_id')
if condition:
domain.append(('receivable_id.tenure_type', '=', condition))
if partner_id:
domain.append(('receivable_id.precinct_id', '=', partner_id))
if company_id:
domain.append(('receivable_id.company_id', '=', company_id))
if management_type_id:
domain.append(('receivable_id.management_type_id', '=', management_type_id))
else:
domain.append(('receivable_id.management_type_id.name', 'not in', ['押金', '违约金']))
lines = self.search(domain, order="receivable_id asc")
rows = []
total_amount = 0.0
for idx, line in enumerate(lines, start=1):
rec = line.receivable_id
contract = rec.contract_id if rec else None
amount = round(line.not_collected or 0.0, 2)
total_amount += amount
# 物业穿透
property_val = ''
if contract and contract.lease_ids and contract.lease_ids[0].address_id:
prop = contract.lease_ids[0].address_id
property_val = {
'model': 'yuthon.property',
'id': prop.id,
'display_name': rec.property_name or prop.name,
}
elif rec:
property_val = rec.property_name or ''
rows.append({
"序号": idx,
"合同编号": {
'model': 'property.lease.contract',
'id': contract.id,
'display_name': contract.contract_code,
} if contract else "",
"物业名称": property_val,
"承租方名称": {
'model': 'property.tenant.information',
'id': rec.lessee_id.id,
'display_name': rec.lessee_id.name,
} if rec and rec.lessee_id else "",
"收款类型": rec.management_type_id.name if rec and rec.management_type_id else "",
"未收金额": amount,
"所属公司": contract.lessor_id.name if contract and contract.lessor_id else "",
"管理员": {
'model': 'res.users',
'id': rec.admin_id.id,
'display_name': rec.admin_id.name,
} if rec and rec.admin_id else "",
})
stats = [
{'name': '记录总数', 'amount': len(rows)},
{'name': '累计欠收', 'amount': round(total_amount, 2)},
]
return {"rows": rows, "stats": stats}
+105
View File
@@ -0,0 +1,105 @@
import datetime
from odoo import api, models
class CumIncomeReport(models.Model):
_inherit = 'receivable.modify.line'
@api.model
def get_cum_income_report(self, params):
"""
累计收入报表(数据源:receivable.modify.line):
- 按年查询
- 取交易金额(original_amount)
- 排除押金和违约金类型
"""
today = datetime.date.today()
current_year = today.year
date_start = params.get("date_start")
# 按年查询:如果有传值则使用该年份,否则使用当前年份
if date_start:
year = datetime.datetime.fromisoformat(date_start).year
else:
year = current_year
# 构建年份的开始和结束日期
year_start = datetime.date(year, 1, 1)
year_end = datetime.date(year, 12, 31)
domain = [
('receivable_id.due_date', '>=', year_start),
('receivable_id.due_date', '<=', year_end),
('receivable_id.is_invalid', '=', False),
]
keyword = params.get("keyword")
if keyword:
domain += [
'|', '|',
('receivable_id.contract_id.contract_code', 'ilike', keyword),
('receivable_id.property_name', 'ilike', keyword),
('receivable_id.lessee_id.name', 'ilike', keyword),
]
condition = params.get('condition')
partner_id = params.get('partner_id')
company_id = params.get('company_id')
management_type_id = params.get('management_type_id')
if condition:
domain.append(('receivable_id.tenure_type', '=', condition))
if partner_id:
domain.append(('receivable_id.precinct_id', '=', partner_id))
if company_id:
domain.append(('receivable_id.company_id', '=', company_id))
if management_type_id:
domain.append(('receivable_id.management_type_id', '=', management_type_id))
else:
domain.append(('receivable_id.management_type_id.name', 'not in', ['押金', '违约金']))
lines = self.search(domain, order="receivable_id asc")
rows = []
total_amount = 0.0
for idx, line in enumerate(lines, start=1):
rec = line.receivable_id
contract = rec.contract_id if rec else None
amount = round(line.original_amount or 0.0, 2)
total_amount += amount
# 物业穿透
property_val = ''
if contract and contract.lease_ids and contract.lease_ids[0].address_id:
prop = contract.lease_ids[0].address_id
property_val = {
'model': 'yuthon.property',
'id': prop.id,
'display_name': rec.property_name or prop.name,
}
elif rec:
property_val = rec.property_name or ''
rows.append({
"序号": idx,
"合同编号": {
'model': 'property.lease.contract',
'id': contract.id,
'display_name': contract.contract_code,
} if contract else "",
"承租方名称": {
'model': 'property.tenant.information',
'id': rec.lessee_id.id,
'display_name': rec.lessee_id.name,
} if rec and rec.lessee_id else "",
"物业名称": property_val,
"金额": amount,
})
stats = [
{'name': '记录总数', 'amount': len(rows)},
{'name': '累计收入', 'amount': round(total_amount, 2)},
]
return {"rows": rows, "stats": stats}
@@ -4,7 +4,7 @@ from odoo import api, models
class CurrentMonthReceivedReport(models.Model):
_inherit = 'property.accounts.receivable'
_inherit = 'receivable.modify.line'
@api.model
def get_current_month_received_report(self, params):
@@ -15,9 +15,9 @@ class CurrentMonthReceivedReport(models.Model):
if keyword:
domain += [
'|', '|',
('contract_id.contract_code', 'ilike', keyword),
('property_name', 'ilike', keyword),
('lessee_id.name', 'ilike', keyword),
('receivable_id.contract_id.contract_code', 'ilike', keyword),
('receivable_id.property_name', 'ilike', keyword),
('receivable_id.lessee_id.name', 'ilike', keyword),
]
date_start = params.get("date_start")
@@ -28,20 +28,20 @@ class CurrentMonthReceivedReport(models.Model):
# 优先按收款日期过滤;若收款日期为空,则按应收日期兜底
domain += [
'|',
'&', ('payout_date', '>=', start_dt), ('payout_date', '<=', end_dt),
'&', ('payout_date', '=', False), '&', ('due_date', '>=', start_dt), ('due_date', '<=', end_dt)
'&', ('receivable_id.payout_date', '>=', start_dt), ('receivable_id.payout_date', '<=', end_dt),
'&', ('receivable_id.payout_date', '=', False), '&', ('receivable_id.due_date', '>=', start_dt), ('receivable_id.due_date', '<=', end_dt)
]
elif start_dt:
domain += [
'|',
('payout_date', '>=', start_dt),
'&', ('payout_date', '=', False), ('due_date', '>=', start_dt)
('receivable_id.payout_date', '>=', start_dt),
'&', ('receivable_id.payout_date', '=', False), ('receivable_id.due_date', '>=', start_dt)
]
elif end_dt:
domain += [
'|',
('payout_date', '<=', end_dt),
'&', ('payout_date', '=', False), ('due_date', '<=', end_dt)
('receivable_id.payout_date', '<=', end_dt),
'&', ('receivable_id.payout_date', '=', False), ('receivable_id.due_date', '<=', end_dt)
]
condition = params.get('condition')
@@ -50,21 +50,22 @@ class CurrentMonthReceivedReport(models.Model):
management_type_id = params.get('management_type_id')
if condition:
domain.append(('tenure_type', '=', condition))
domain.append(('receivable_id.tenure_type', '=', condition))
if partner_id:
domain.append(('precinct_id', '=', partner_id))
domain.append(('receivable_id.precinct_id', '=', partner_id))
if company_id:
domain.append(('company_id', '=', company_id))
domain.append(('receivable_id.company_id', '=', company_id))
if management_type_id:
domain.append(('management_type_id', '=', management_type_id))
domain.append(('receivable_id.management_type_id', '=', management_type_id))
receivables = self.search(domain, order="payout_date asc, due_date asc")
lines = self.search(domain, order="receivable_id asc")
rows = []
total_amount = 0.0
for idx, rec in enumerate(receivables, start=1):
amount = rec.delivered1 or 0.0 # 已收金额
for idx, line in enumerate(lines, start=1):
rec = line.receivable_id
amount = line.amount or 0.0 # 已对金额
total_amount += amount
rows.append({
@@ -73,24 +74,24 @@ class CurrentMonthReceivedReport(models.Model):
'model': 'property.lease.contract',
'id': rec.contract_id.id,
'display_name': rec.contract_id.contract_code,
} if rec.contract_id else "",
} if rec and rec.contract_id else "",
"物业名称": {
'model': 'yuthon.property',
'id': rec.contract_id.lease_ids[0].address_id.id,
'display_name': rec.property_name,
} if rec.contract_id and rec.contract_id.lease_ids and rec.contract_id.lease_ids[0].address_id else rec.property_name or "",
} if rec and rec.contract_id and rec.contract_id.lease_ids and rec.contract_id.lease_ids[0].address_id else rec.property_name or "",
"承租方名称": {
'model': 'property.tenant.information',
'id': rec.lessee_id.id,
'display_name': rec.lessee_id.name,
} if rec.lessee_id else "",
} if rec and rec.lessee_id else "",
"金额": round(amount, 2),
"所属公司": rec.company_id.referred if rec.company_id and hasattr(rec.company_id, 'referred') else rec.company_id.name if rec.company_id else "",
"所属公司": rec.company_id.referred if rec and rec.company_id and hasattr(rec.company_id, 'referred') else rec.company_id.name if rec and rec.company_id else "",
"管理员": {
'model': 'res.users',
'id': rec.admin_id.id,
'display_name': rec.admin_id.name,
} if rec.admin_id else "",
} if rec and rec.admin_id else "",
})
return {
+93
View File
@@ -0,0 +1,93 @@
from odoo import models, fields, api
from datetime import datetime, date
class HydropowerReport(models.Model):
_name = 'hydropower.report'
_description = '水电表应收明细表'
_auto = False
property_name = fields.Char(string='房屋名称')
lessee_name = fields.Char(string='业户名称')
water_fee = fields.Float(string='水费')
water_period = fields.Char(string='水费期间')
electric_fee = fields.Float(string='电费')
electric_period = fields.Char(string='电费期间')
total = fields.Float(string='小计')
@api.model
def get_hydropower_data(self, params):
"""获取水电表数据,接收 params 字典"""
date_start = params.get('date_start')
date_end = params.get('date_end')
start_date = datetime.fromisoformat(date_start).date() if date_start else date.today().replace(day=1)
end_date = datetime.fromisoformat(date_end).date() if date_end else date.today()
# 获取水表数据
water_data = self.env['meter.water.line'].search([
('meter_date', '>=', start_date),
('meter_date', '<=', end_date),
('state', '=', 'reading')
])
# 获取电表数据
electric_data = self.env['meter.archives.line'].search([
('meter_date', '>=', start_date),
('meter_date', '<=', end_date),
('state', '=', 'reading')
])
# 整合数据
property_dict = {}
for water in water_data:
for property_id in water.yuthon_property_ids:
key = property_id.name
if key not in property_dict:
property_dict[key] = {
'房屋名称': property_id.name,
'业户名称': property_id.lessee_id.name if property_id.lessee_id else '',
'水费': 0.0,
'水费期间': '',
'电费': 0.0,
'电费期间': '',
'小计': 0.0
}
property_dict[key]['水费'] += water.receivables
property_dict[key]['水费期间'] = f"{start_date}到{end_date}"
for electric in electric_data:
for property_id in electric.yuthon_property_ids:
key = property_id.name
if key not in property_dict:
property_dict[key] = {
'房屋名称': property_id.name,
'业户名称': property_id.lessee_id.name if property_id.lessee_id else '',
'水费': 0.0,
'水费期间': '',
'电费': 0.0,
'电费期间': '',
'小计': 0.0
}
property_dict[key]['电费'] += electric.receivables
property_dict[key]['电费期间'] = f"{start_date}到{end_date}"
rows = []
total_water = 0.0
total_electric = 0.0
for idx, (key, value) in enumerate(property_dict.items(), start=1):
value['小计'] = round(value['水费'] + value['电费'], 2)
value['水费'] = round(value['水费'], 2)
value['电费'] = round(value['电费'], 2)
total_water += value['水费']
total_electric += value['电费']
rows.append(value)
stats = [
{'name': '记录数', 'amount': len(rows)},
{'name': '水费合计', 'amount': round(total_water, 2)},
{'name': '电费合计', 'amount': round(total_electric, 2)},
{'name': '合计', 'amount': round(total_water + total_electric, 2)},
]
return {'rows': rows, 'stats': stats}
@@ -1,90 +1,121 @@
import datetime
import calendar
from odoo import api, models
class MonthlyPrecollectionReport(models.Model):
_inherit = 'property.accounts.receivable'
_inherit = 'receivable.modify.line'
@api.model
def get_monthly_precollection_report(self, params):
"""汇总当前筛选条件下的应收数据(本月预收视图)。"""
domain = []
"""
本月预收报表(数据源:receivable.modify.line):
- 应收开始日期(receivable_id.due_date)在查询月当月(月初 ~ 月末)
- delivered1 > original_amount(已交超过当期金额,即有预收)
- 应收账单非作废:receivable_id.is_invalid = False
- 排除押金和违约金类型
- 金额取 delivered1 - original_amount(预收金额)
"""
today = datetime.date.today()
date_start = params.get("date_start")
if date_start:
query_date = datetime.datetime.fromisoformat(date_start).date()
else:
query_date = today
# 查询月月初和月末
month_start = query_date.replace(day=1)
last_day = calendar.monthrange(query_date.year, query_date.month)[1]
month_end = query_date.replace(day=last_day)
domain = [
('receivable_id.due_date', '>=', month_start),
('receivable_id.due_date', '<=', month_end),
('receivable_id.delivered1', '>', 0),
('receivable_id.is_invalid', '=', False),
]
keyword = params.get("keyword")
if keyword:
domain += [
'|', '|',
('lessee_id.name', 'ilike', keyword),
('property_name', 'ilike', keyword),
('title_deed', 'ilike', keyword),
('receivable_id.contract_id.contract_code', 'ilike', keyword),
('receivable_id.property_name', 'ilike', keyword),
('receivable_id.lessee_id.name', 'ilike', keyword),
]
date_start = params.get("date_start")
date_end = params.get("date_end")
if date_start:
start_dt = datetime.datetime.fromisoformat(date_start).date()
domain.append(("due_date", ">=", start_dt))
if date_end:
end_dt = datetime.datetime.fromisoformat(date_end).date()
domain.append(("due_date", "<=", end_dt))
condition = params.get('condition')
partner_id = params.get('partner_id')
company_id = params.get('company_id')
management_type_id = params.get('management_type_id')
if condition:
domain.append(('tenure_type', '=', condition))
domain.append(('receivable_id.tenure_type', '=', condition))
if partner_id:
domain.append(('precinct_id', '=', partner_id))
domain.append(('receivable_id.precinct_id', '=', partner_id))
if company_id:
domain.append(('company_id', '=', company_id))
domain.append(('receivable_id.company_id', '=', company_id))
if management_type_id:
domain.append(('management_type_id', '=', management_type_id))
domain.append(('receivable_id.management_type_id', '=', management_type_id))
else:
domain.append(('receivable_id.management_type_id.name', 'not in', ['押金', '违约金']))
receivables = self.search(domain, limit=1000, order="due_date asc")
lines = self.search(domain, order="receivable_id asc")
result = []
rows = []
total_amount = 0.0
for index, rec in enumerate(receivables, start=1):
amount = rec.original_amount or 0.0
idx = 1
for line in lines:
rec = line.receivable_id
contract = rec.contract_id if rec else None
# 预收金额 = 已交 - 当期金额,必须 > 0 才是真正预收
amount = round((rec.delivered1 or 0.0) - (rec.original_amount or 0.0), 2)
if amount <= 0:
continue
total_amount += amount
result.append({
"序号": index,
# 物业穿透
property_val = ''
if contract and contract.lease_ids and contract.lease_ids[0].address_id:
prop = contract.lease_ids[0].address_id
property_val = {
'model': 'yuthon.property',
'id': prop.id,
'display_name': rec.property_name or prop.name,
}
elif rec:
property_val = rec.property_name or ''
rows.append({
"序号": idx,
"合同编号": {
'model': 'property.lease.contract',
'id': rec.contract_id.id,
'display_name': rec.contract_id.contract_code,
} if rec.contract_id else "",
"物业名称": {
'model': 'yuthon.property',
'id': rec.contract_id.lease_ids[0].address_id.id,
'display_name': rec.property_name,
} if rec.contract_id and rec.contract_id.lease_ids and rec.contract_id.lease_ids[0].address_id else rec.property_name or "",
'id': contract.id,
'display_name': contract.contract_code,
} if contract else "",
"物业名称": property_val,
"承租方名称": {
'model': 'property.tenant.information',
'id': rec.lessee_id.id,
'display_name': rec.lessee_id.name,
} if rec.lessee_id else "",
"金额": round(amount, 2),
"所属公司": (
rec.contract_id.lessor_id.referred
or rec.contract_id.lessor_id.name
if rec.contract_id and rec.contract_id.lessor_id
else ""
),
} if rec and rec.lessee_id else "",
"收款类型": rec.management_type_id.name if rec and rec.management_type_id else "",
"金额": amount,
"所属公司": contract.lessor_id.name if contract and contract.lessor_id else "",
"管理员": {
'model': 'res.users',
'id': rec.contract_id.admin_id.id,
'display_name': rec.contract_id.admin_id.name,
} if rec.contract_id and rec.contract_id.admin_id else "",
'id': rec.admin_id.id,
'display_name': rec.admin_id.name,
} if rec and rec.admin_id else "",
})
idx += 1
return {
"rows": result,
"stats": [
{"name": "应收总额", "amount": round(total_amount, 2)},
],
}
stats = [
{'name': '合同总数', 'amount': len(rows)},
{'name': '预收总额', 'amount': round(total_amount, 2)},
]
return {"rows": rows, "stats": stats}
+10 -7
View File
@@ -11,11 +11,10 @@ class PreAdvanceReport(models.Model):
"""
前期预收报表(数据源:receivable.modify.line):
- 应收开始日期(receivable_id.due_date)在查询月最后一天之后(未来应收)
- 应收账单未收金额(receivable_id.submitted)= 0(已全额收款)
- original_amount > 0(有交易金额)
- delivered1 > original_amount(已交超过当期金额,即有预收)
- 应收账单非作废:receivable_id.is_invalid = False
- 排除押金和违约金类型
- 金额取 original_amount(交易金额)
- 金额取 delivered1 - original_amount(预收金额)
"""
today = datetime.date.today()
if today.month == 12:
@@ -30,8 +29,7 @@ class PreAdvanceReport(models.Model):
domain = [
('receivable_id.due_date', '>', cutoff_date),
('receivable_id.submitted', '=', 0),
('original_amount', '>', 0),
('receivable_id.delivered1', '>', 0),
('receivable_id.is_invalid', '=', False),
]
@@ -64,10 +62,14 @@ class PreAdvanceReport(models.Model):
rows = []
total_amount = 0.0
for idx, line in enumerate(lines, start=1):
idx = 1
for line in lines:
rec = line.receivable_id
contract = rec.contract_id if rec else None
amount = round(line.original_amount or 0.0, 2)
# 预收金额 = 已交 - 当期金额,必须 > 0 才是真正预收
amount = round((rec.delivered1 or 0.0) - (rec.original_amount or 0.0), 2)
if amount <= 0:
continue
total_amount += amount
# 物业穿透
@@ -103,6 +105,7 @@ class PreAdvanceReport(models.Model):
'display_name': rec.admin_id.name,
} if rec and rec.admin_id else "",
})
idx += 1
stats = [
{'name': '合同总数', 'amount': len(rows)},
+1 -1
View File
@@ -61,7 +61,7 @@ class PreArrearsReport(models.Model):
for idx, line in enumerate(lines, start=1):
rec = line.receivable_id
contract = rec.contract_id if rec else None
amount = round(line.original_amount or 0.0, 2)
amount = round(rec.submitted or 0.0, 2)
total_amount += amount
# 物业穿透
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="action_cum_arrears_report" model="ir.actions.client">
<field name="name">累计欠收</field>
<field name="tag">receivable_report_client</field>
<field name="context">
{
"sideBar": true,
"report_config": {
"model": "receivable.modify.line",
"method": "get_cum_arrears_report",
"dateMode": "single",
"columns": [
{"headerName": "序号", "field": "序号", "width": 45, "flex": 0, "pinned": "left"},
{"headerName": "合同编号", "field": "合同编号", "flex": 1.2},
{"headerName": "物业名称", "field": "物业名称", "flex": 2.5},
{"headerName": "承租方名称", "field": "承租方名称", "flex": 1.4},
{"headerName": "收款类型", "field": "收款类型", "flex": 1},
{"headerName": "未收金额", "field": "未收金额", "flex": 1, "type": "number"},
{"headerName": "所属公司", "field": "所属公司", "flex": 1},
{"headerName": "管理员", "field": "管理员", "flex": 1}
]
}
}
</field>
</record>
</data>
</odoo>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="action_cum_income_report" model="ir.actions.client">
<field name="name">累计收入</field>
<field name="tag">receivable_report_client</field>
<field name="context">{"report_config": {"model": "receivable.modify.line", "method": "get_cum_income_report", "columns": [{"headerName": "序号", "field": "序号", "width": 80, "flex": 0}, {"headerName": "合同编号", "field": "合同编号", "flex": 1}, {"headerName": "承租方名称", "field": "承租方名称", "flex": 1}, {"headerName": "物业名称", "field": "物业名称", "flex": 1}, {"headerName": "金额", "field": "金额", "flex": 1}]}}</field>
</record>
</odoo>
@@ -8,7 +8,7 @@
{
"sideBar": true,
"report_config": {
"model": "property.accounts.receivable",
"model": "receivable.modify.line",
"method": "get_current_month_received_report",
"columns": [
{"headerName": "序号", "field": "序号", "width": 45, "flex": 0, "pinned": "left"},
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
<!-- 水电表应收明细表动作 -->
<record id="action_hydropower_report" model="ir.actions.client">
<field name="name">水电表应收明细表</field>
<field name="tag">receivable_report_client</field>
<field name="context">{"sideBar": true, "report_config": {"model": "hydropower.report", "method": "get_hydropower_data", "dateMode": "range", "columns": [{"headerName": "房屋名称", "field": "房屋名称", "flex": 2}, {"headerName": "业户名称", "field": "业户名称", "flex": 1.5}, {"headerName": "水费", "field": "水费", "flex": 1, "type": "number"}, {"headerName": "水费期间", "field": "水费期间", "flex": 2}, {"headerName": "电费", "field": "电费", "flex": 1, "type": "number"}, {"headerName": "电费期间", "field": "电费期间", "flex": 2}, {"headerName": "小计", "field": "小计", "flex": 1, "type": "number"}]}}</field>
</record>
<!-- 水电表应收明细表菜单 -->
<menuitem id="yuthon_property_hydropower_menu"
sequence="5"
name="水电表应收明细"
parent="yuthon_property_approval.property_approval_report_menu"
action="action_hydropower_report"/>
</data>
</odoo>
+8 -12
View File
@@ -48,30 +48,26 @@
name="本月实收总额"
parent="yuthon_property_income_menu"
action="action_total_received_report"/>
<menuitem id="yuthon_property_income_month_received_menu"
sequence="6"
name="本月实收"
parent="yuthon_property_income_menu"/>
<menuitem id="yuthon_property_income_month_received_belong_menu"
sequence="1"
sequence="6"
name="归属本月"
parent="yuthon_property_income_month_received_menu"
parent="yuthon_property_income_menu"
action="action_current_month_received_report"/>
<menuitem id="yuthon_property_income_month_received_advance_menu"
sequence="2"
sequence="7"
name="本月预收"
parent="yuthon_property_income_month_received_menu"
parent="yuthon_property_income_menu"
action="action_monthly_precollection_report"/>
<menuitem id="yuthon_property_income_cum_arrears_menu"
sequence="7"
sequence="8"
name="累计欠收"
parent="yuthon_property_income_menu"
action="action_report_placeholder"/>
action="action_cum_arrears_report"/>
<menuitem id="yuthon_property_income_cum_income_menu"
sequence="8"
sequence="9"
name="累计收入"
parent="yuthon_property_income_menu"
action="action_report_placeholder"/>
action="action_cum_income_report"/>
<!-- 其他收入 -->
<menuitem id="yuthon_property_other_income_menu"
@@ -8,13 +8,15 @@
{
"sideBar": true,
"report_config": {
"model": "property.accounts.receivable",
"model": "receivable.modify.line",
"method": "get_monthly_precollection_report",
"dateMode": "single",
"columns": [
{"headerName": "序号", "field": "序号", "width": 45, "flex": 0, "pinned": "left"},
{"headerName": "合同编号", "field": "合同编号", "flex": 1},
{"headerName": "物业名称", "field": "物业名称", "flex": 2},
{"headerName": "承租方名称", "field": "承租方名称", "flex": 1.2},
{"headerName": "合同编号", "field": "合同编号", "flex": 1.2},
{"headerName": "物业名称", "field": "物业名称", "flex": 2.5},
{"headerName": "承租方名称", "field": "承租方名称", "flex": 1.4},
{"headerName": "收款类型", "field": "收款类型", "flex": 1},
{"headerName": "金额", "field": "金额", "flex": 1, "type": "number"},
{"headerName": "所属公司", "field": "所属公司", "flex": 1},
{"headerName": "管理员", "field": "管理员", "flex": 1}
+76 -2
View File
@@ -9,8 +9,8 @@ class YuthonMealExpenses(models.Model):
employee_id = fields.Many2one('hr.employee', string='姓名')
card = fields.Char(string='身份证')
number_breakfasts = fields.Integer(string='早餐次数')
number_lunches = fields.Integer(string='午餐次数')
number_breakfasts = fields.Integer(string='早餐次数', compute='_compute_catering_counts', store=True)
number_lunches = fields.Integer(string='午餐次数', compute='_compute_catering_counts', store=True)
total_payment = fields.Float(string='合计缴费', compute='_compute_total_payment', store=True)
company_id = fields.Many2one(related='employee_id.company_id', string="公司")
select_time = fields.Selection([('1', '一月'), ('2', '二月'), ('3', '三月'), ('4', '四月'),
@@ -24,6 +24,30 @@ class YuthonMealExpenses(models.Model):
toll_state = fields.Selection([('to_do', '待缴'), ('done', '已缴')], string='缴费状态', default='to_do')
def _get_date_range(self):
year = int(self.select_year)
month = int(self.select_time)
last_day = calendar.monthrange(year, month)[1]
date_start = '%04d-%02d-01' % (year, month)
date_end = '%04d-%02d-%02d' % (year, month, last_day)
return date_start, date_end
@api.depends('employee_id', 'select_year', 'select_time')
def _compute_catering_counts(self):
for record in self:
if not record.employee_id or not record.select_year or not record.select_time:
record.number_breakfasts = 0
record.number_lunches = 0
continue
date_start, date_end = record._get_date_range()
caterings = self.env['yuthon.catering'].search([
('employee_id', '=', record.employee_id.id),
('date', '>=', date_start),
('date', '<=', date_end),
])
record.number_breakfasts = len(caterings.filtered(lambda c: c.breakfast))
record.number_lunches = len(caterings.filtered(lambda c: c.lunch))
@api.depends('number_breakfasts', 'number_lunches')
def _compute_total_payment(self):
for record in self:
@@ -54,3 +78,53 @@ class YuthonMealExpenses(models.Model):
'skip_party_month_onchange': True
},
}
def action_generate_missing_feb(self):
target_year = '2026'
target_month = '2'
# 取全部参餐员工
all_confs = self.env['yuthon.catering.conf'].search([])
all_employees = all_confs.mapped('employee_ids')
# 已有记录的员工
existing = self.search([
('select_year', '=', target_year),
('select_time', '=', target_month),
])
existing_emp_ids = existing.mapped('employee_id').ids
# 批量创建缺失记录
created = 0
for emp in all_employees:
if emp.id not in existing_emp_ids:
self.create({
'employee_id': emp.id,
'card': emp.identification_id or '',
'select_year': target_year,
'select_time': target_month,
})
created += 1
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': '生成完成',
'message': '共生成2026年2月缺失数据 %d 条' % created,
'type': 'success',
'sticky': False,
}
}
def action_view_detail(self):
self.ensure_one()
date_start, date_end = self._get_date_range()
catering_tree_view = self.env.ref('yuthon_catering.yuthon_catering_tree_view')
return {
'type': 'ir.actions.act_window',
'name': '%s年%s月 %s 餐饮明细' % (self.select_year, self.select_time, self.employee_id.name),
'res_model': 'yuthon.catering',
'view_mode': 'tree',
'views': [(catering_tree_view.id, 'tree')],
'target': 'new',
'domain': [('employee_id', '=', self.employee_id.id),
('date', '>=', date_start),
('date', '<=', date_end)],
}
@@ -24,6 +24,9 @@
<field name="model">yuthon.meal.expenses</field>
<field name="arch" type="xml">
<tree string="餐费" editable="bottom" js_class="custom_meal_button">
<header>
<button name="action_generate_missing_feb" string="批量生成2026年2月缺失数据" type="object" class="btn btn-blue"/>
</header>
<field name="select_year"/>
<field name="select_time"/>
<field name="employee_id"/>
@@ -34,6 +37,7 @@
<field name="company_id"/>
<field name="toll_state"/>
<field name="meal_state"/>
<button name="action_view_detail" string="明细" type="object" class="btn btn-blue"/>
<button name="send_meal" string="发送" type="object" class="btn btn-blue"/>
</tree>
</field>