diff --git a/jikimo_purchase_request/__manifest__.py b/jikimo_purchase_request/__manifest__.py index d919b25d..dad34ddc 100644 --- a/jikimo_purchase_request/__manifest__.py +++ b/jikimo_purchase_request/__manifest__.py @@ -9,6 +9,7 @@ 'depends': ['sf_manufacturing', 'purchase_request'], 'data': [ 'views/sale_order_view.xml', + 'views/mrp_production.xml', 'views/purchase_request_view.xml', 'wizard/purchase_request_line_make_purchase_order_view.xml', ], diff --git a/jikimo_purchase_request/models/__init__.py b/jikimo_purchase_request/models/__init__.py index d27a3cd0..433b063d 100644 --- a/jikimo_purchase_request/models/__init__.py +++ b/jikimo_purchase_request/models/__init__.py @@ -2,5 +2,6 @@ from . import product_template from . import purchase_request from . import sale_order +from . import mrp_production from . import purchase_order from . import stock_rule diff --git a/jikimo_purchase_request/models/mrp_production.py b/jikimo_purchase_request/models/mrp_production.py new file mode 100644 index 00000000..c1c0feb9 --- /dev/null +++ b/jikimo_purchase_request/models/mrp_production.py @@ -0,0 +1,39 @@ +from odoo import fields, models, api, _ + + +class MrpProduction(models.Model): + _inherit = 'mrp.production' + + pr_mp_count = fields.Integer('采购申请单数量', compute='_compute_pr_mp_count', store=True) + + @api.depends('state') + def _compute_pr_mp_count(self): + for item in self: + pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', item.name)]) + if pr_ids: + item.pr_mp_count = len(pr_ids) + else: + item.pr_mp_count = 0 + + def action_view_pr_mp(self): + """ + 采购请求 + """ + self.ensure_one() + pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', self.name)]) + action = { + 'res_model': 'purchase.request', + 'type': 'ir.actions.act_window', + } + if len(pr_ids) == 1: + action.update({ + 'view_mode': 'form', + 'res_id': pr_ids[0].id, + }) + else: + action.update({ + 'name': _("从 %s生成采购请求单", self.name), + 'domain': [('id', 'in', pr_ids)], + 'view_mode': 'tree,form', + }) + return action diff --git a/jikimo_purchase_request/models/purchase_request.py b/jikimo_purchase_request/models/purchase_request.py index 4b5f86ba..9e610612 100644 --- a/jikimo_purchase_request/models/purchase_request.py +++ b/jikimo_purchase_request/models/purchase_request.py @@ -1,4 +1,5 @@ import re +import ast from odoo import models, fields, api @@ -20,6 +21,13 @@ class PurchaseRequest(models.Model): if pr.state != 'draft' and pr.rule_new_add: pr.rule_new_add = False + def action_view_purchase_order(self): + action = super(PurchaseRequest, self).action_view_purchase_order() + origin_context = ast.literal_eval(action['context']) + if 'search_default_draft' in origin_context: + origin_context.pop('search_default_draft') + action['context'] = origin_context + return action class PurchaseRequestLine(models.Model): _inherit = 'purchase.request.line' diff --git a/jikimo_purchase_request/views/mrp_production.xml b/jikimo_purchase_request/views/mrp_production.xml new file mode 100644 index 00000000..0f59a396 --- /dev/null +++ b/jikimo_purchase_request/views/mrp_production.xml @@ -0,0 +1,21 @@ + + + + mrp.production.inherited.form.purchase.request + mrp.production + + + + + + + + \ No newline at end of file diff --git a/jikimo_purchase_request_tier_validation/__init__.py b/jikimo_purchase_request_tier_validation/__init__.py new file mode 100644 index 00000000..cde864ba --- /dev/null +++ b/jikimo_purchase_request_tier_validation/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import models diff --git a/jikimo_purchase_request_tier_validation/__manifest__.py b/jikimo_purchase_request_tier_validation/__manifest__.py new file mode 100644 index 00000000..403297ab --- /dev/null +++ b/jikimo_purchase_request_tier_validation/__manifest__.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +{ + 'name': "机企猫 采购审批流程", + + 'summary': """ + Short (1 phrase/line) summary of the module's purpose, used as + subtitle on modules listing or apps.openerp.com""", + + 'description': """ + Long description of module's purpose + """, + + 'author': "My Company", + 'website': "https://www.yourcompany.com", + + # Categories can be used to filter modules in modules listing + # Check https://github.com/odoo/odoo/blob/16.0/odoo/addons/base/data/ir_module_category_data.xml + # for the full list + 'category': 'Uncategorized', + 'version': '0.1', + + # any module necessary for this one to work correctly + 'depends': ['purchase_request_tier_validation'], + + # always loaded + 'data': [ + ], +} diff --git a/jikimo_purchase_request_tier_validation/models/__init__.py b/jikimo_purchase_request_tier_validation/models/__init__.py new file mode 100644 index 00000000..5305644d --- /dev/null +++ b/jikimo_purchase_request_tier_validation/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import models \ No newline at end of file diff --git a/jikimo_purchase_request_tier_validation/models/models.py b/jikimo_purchase_request_tier_validation/models/models.py new file mode 100644 index 00000000..0070a4c7 --- /dev/null +++ b/jikimo_purchase_request_tier_validation/models/models.py @@ -0,0 +1,24 @@ +from odoo import models, fields, api, _ +from odoo.exceptions import ValidationError +import logging + +_logger = logging.getLogger(__name__) + + +class PurchaseRequest(models.Model): + _inherit = 'purchase.request' + + + def _validate_tier(self, tiers=False): + res = super(PurchaseRequest, self)._validate_tier(tiers) + + # 检查是否所有审批都已通过 + all_approved = all( + tier_review.status == 'approved' + for tier_review in self.review_ids + ) + + if self.review_ids and all_approved: # 确保有审批记录 + self.state = 'approved' + + return res diff --git a/jikimo_purchase_tier_validation/__manifest__.py b/jikimo_purchase_tier_validation/__manifest__.py index 4d676d8f..ed0cecb7 100644 --- a/jikimo_purchase_tier_validation/__manifest__.py +++ b/jikimo_purchase_tier_validation/__manifest__.py @@ -1,14 +1,12 @@ # -*- coding: utf-8 -*- { - 'name': "机企猫 采购审批流程", + 'name': "机企猫 采购申请审批流程", 'summary': """ - Short (1 phrase/line) summary of the module's purpose, used as - subtitle on modules listing or apps.openerp.com""", + 采购申请审批流程""", 'description': """ - Long description of module's purpose - """, + 采购申请审批流程""", 'author': "My Company", 'website': "https://www.yourcompany.com", diff --git a/quality_control/controllers/main.py b/quality_control/controllers/main.py index 5bbe71a9..95b11f5c 100644 --- a/quality_control/controllers/main.py +++ b/quality_control/controllers/main.py @@ -90,25 +90,31 @@ class QualityController(http.Controller): ('Access-Control-Allow-Headers', 'Content-Type, Authorization') ] ) - + + class QualityReportController(http.Controller): - - @http.route('/quality/report/', type='http', auth='public') - def get_public_report(self, attachment_id, **kw): + @http.route('/quality/report/', type='http', auth='public') + def get_public_report(self, document_id, **kw): """提供公开访问PDF报告的控制器""" - attachment = request.env['ir.attachment'].sudo().browse(int(attachment_id)) - - # 安全检查:确保只有质检报告附件可以被访问 - if attachment.exists() and 'QC-' in attachment.name: - # 解码Base64数据为二进制数据 - pdf_content = base64.b64decode(attachment.datas) - - # 返回解码后的PDF内容 + document = request.env['documents.document'].sudo().browse(int(document_id)) + + # 安全检查:确保只有质检报告文档可以被访问 + if document.exists() and document.res_model == 'quality.check': + # 获取PDF内容 + pdf_content = document.raw + + # 返回PDF内容 return request.make_response( pdf_content, headers=[ ('Content-Type', 'application/pdf'), - ('Content-Disposition', f'inline; filename={attachment.name}') + ('Content-Disposition', f'inline; filename={document.name}.pdf') ] ) - return request.not_found() \ No newline at end of file + return request.not_found() + + @http.route('/quality/report/not_published', type='http', auth='public') + def get_not_published_report(self, **kw): + """提供未发布报告的控制器""" + return "报告尚未发布" + diff --git a/quality_control/models/quality.py b/quality_control/models/quality.py index 022dc6b7..412305b2 100644 --- a/quality_control/models/quality.py +++ b/quality_control/models/quality.py @@ -183,10 +183,12 @@ class QualityCheck(models.Model): report_number_name = fields.Char('出厂检验报告编号名称', compute='_compute_report_number_name') old_report_name = fields.Char('旧出厂检验报告编号', default='') + @api.depends('serial_number', 'part_number') def _compute_report_number_name(self): for record in self: - str_serial_number = '0' + str(record.serial_number) if record.serial_number < 10 else str(record.serial_number) + str_serial_number = '0' + str(record.serial_number) if record.serial_number < 10 else str( + record.serial_number) str_part_number = record.part_number if record.part_number else '' record.report_number_name = f'FQC{str_part_number}{str_serial_number}' @@ -284,25 +286,7 @@ class QualityCheck(models.Model): """实际执行发布操作的方法""" self.ensure_one() - # 1. 获取报告动作 - report_action = self.env.ref('sf_quality.action_report_quality_inspection') - - # 2. 生成PDF报告 - 修改这里的调用方式 - pdf_content, _ = report_action._render_qweb_pdf( - report_ref=report_action.report_name, # 添加report_ref参数 - res_ids=self.ids - ) - - # attachment = self.env['ir.attachment'].create({ - # 'name': f'{self.name}.pdf', - # 'type': 'binary', - # 'datas': b64encode(pdf_content), - # 'res_model': self._name, - # 'res_id': self.id, - # 'mimetype': 'application/pdf', - # }) - - # 获取已发布的文档文件夹 + # 1. 获取已发布的文档文件夹 workspace = self.env['documents.folder'].search( [('parent_folder_id', '=', self.env.ref('sf_quality.documents_purchase_contracts_folder').id), ('name', '=', '已发布')], limit=1) @@ -310,11 +294,9 @@ class QualityCheck(models.Model): if self.serial_number > 99: raise UserError(_('流水号不能大于99')) - # 3. 创建文档记录 + # 2. 先创建空文档记录 doc_vals = { 'name': self.report_number_name, - 'raw': pdf_content, - # 'attachment_id': attachment.id, 'mimetype': 'application/pdf', 'res_id': self.id, 'folder_id': workspace.id, @@ -322,13 +304,26 @@ class QualityCheck(models.Model): } doc = self.env['documents.document'].create(doc_vals) - # 关联到当前质检记录 + + # 3. 关联文档到质检记录 self.write({ 'report_number_id': doc.id, 'quality_state': 'pass' }) - # 记录发布历史 + # 4. 获取报告动作并生成PDF(此时二维码将包含正确的文档ID) + report_action = self.env.ref('sf_quality.action_report_quality_inspection') + pdf_content, _ = report_action._render_qweb_pdf( + report_ref=report_action.report_name, + res_ids=self.ids + ) + + # 5. 更新文档内容 + doc.write({ + 'raw': pdf_content + }) + + # 6. 记录发布历史 self.env['quality.check.report.history'].create({ 'check_id': self.id, 'report_number_id': doc.id, @@ -339,18 +334,17 @@ class QualityCheck(models.Model): 'sequence': len(self.report_history_ids) + 1 }) - # 更新流水号 + # 7. 更新其他信息 self.serial_number += 1 self.quality_manager = self.env.user.id if self.publish_status == 'canceled' and self.picking_id.state == 'done': self.upload_factory_report() - + self.write({ 'publish_status': 'published', }) - # 返回成功消息 return True # 发布前检验零件图号、操机员、质检员 @@ -397,7 +391,7 @@ class QualityCheck(models.Model): self.report_number_id.write({ 'folder_id': self.env.ref('sf_quality.documents_purchase_contracts_folder_canceled').id, }) - + # 3. 记录发布历史 self.env['quality.check.report.history'].create({ 'check_id': self.id, @@ -408,7 +402,7 @@ class QualityCheck(models.Model): 'document_status': 'canceled', 'sequence': len(self.report_history_ids) + 1 }) - + self.write({ 'old_report_name': self.report_number_id.name }) @@ -442,7 +436,7 @@ class QualityCheck(models.Model): width=140, height=140) ) ) - + def get_latest_report_attachment(self, check_id): """获取指定质检记录的最新报告附件,并删除旧的报告附件""" # 查找特定质检记录的所有附件 @@ -459,20 +453,17 @@ class QualityCheck(models.Model): # 返回最新的附件(如果存在) return attachments and attachments[0] or False - + def get_report_url(self): - """生成报告访问URL,确保获取最新版本""" + """生成报告访问URL""" + self.ensure_one() base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') - report_url = f"{base_url}/web/content/ir.attachment" - - # 获取最新附件的ID - latest_attachment = self.get_latest_report_attachment(self.id) - if latest_attachment: - # 生成包含附件ID的URL - print(f"{base_url}/quality/report/{latest_attachment.id}") - return f"{base_url}/quality/report/{latest_attachment.id}" - return False - + if self.report_number_id: + print(f"{base_url}/quality/report/{self.report_number_id.id}") + return f"{base_url}/quality/report/{self.report_number_id.id}" + else: + return f"{base_url}/quality/report/not_published" + def upload_factory_report(self): """ 上传出厂检验报告到加工订单明细中 @@ -481,39 +472,40 @@ class QualityCheck(models.Model): self.ensure_one() if not self.report_content: raise UserError(_('当前质检单没有出厂检验报告,请先发布报告')) - + if not self.product_id.model_name: raise UserError(_('产品模型名称为空')) - + if not self.picking_id or not self.picking_id.origin: raise UserError(_('无法找到相关的调拨单或来源单据')) - + # 获取订单号(从调拨单的来源字段获取) order_ref = self.picking_id.retrospect_ref - + try: # 准备请求数据 payload = { "order_ref": order_ref, "model_name": self.product_id.model_name, - "report_file": self.report_content.decode('utf-8') if isinstance(self.report_content, bytes) else self.report_content + "report_file": self.report_content.decode('utf-8') if isinstance(self.report_content, + bytes) else self.report_content } - + # 将Python字典转换为JSON字符串 json_data = json.dumps(payload) - + # 获取服务器URL base_url = self.env['ir.config_parameter'].sudo().get_param('bfm_url_new') api_url = f"{base_url}/api/report/create" - + # 设置请求头 headers = { 'Content-Type': 'application/json', } - + # 发送POST请求 response = requests.post(api_url, data=json_data, headers=headers) - + # 处理响应 if response.status_code == 200: result = response.json() @@ -536,10 +528,10 @@ class QualityCheck(models.Model): else: # HTTP请求失败 raise UserError(_('请求失败,状态码: %s') % response.status_code) - + except Exception as e: raise UserError(_('上传过程中发生错误: %s') % str(e)) - + def delete_factory_report(self): """ 删除加工订单明细中的出厂检验报告 @@ -549,7 +541,7 @@ class QualityCheck(models.Model): if not order_ref: raise UserError(_('无法找到相关的调拨单或来源单据')) - + if not self.product_id.model_name: raise UserError(_('产品模型名称为空')) @@ -558,11 +550,11 @@ class QualityCheck(models.Model): payload = { "order_ref": order_ref, "model_name": self.product_id.model_name - } - + } + # 将Python字典转换为JSON字符串 json_data = json.dumps(payload) - + # 获取服务器URL base_url = self.env['ir.config_parameter'].sudo().get_param('bfm_url_new') api_url = f"{base_url}/api/report/delete" @@ -597,10 +589,9 @@ class QualityCheck(models.Model): else: # HTTP请求失败 raise UserError(_('请求失败,状态码: %s') % response.status_code) - + except Exception as e: - raise UserError(_('删除过程中发生错误: %s') % str(e)) - + raise UserError(_('删除过程中发生错误: %s') % str(e)) @depends('product_id') def _compute_material_name(self): diff --git a/sf_base/models/base.py b/sf_base/models/base.py index d7a71e1e..54a1fe1f 100644 --- a/sf_base/models/base.py +++ b/sf_base/models/base.py @@ -421,3 +421,4 @@ class EmbryoRedundancy(models.Model): width = fields.Float('宽度(mm)', required=True) height = fields.Float('高度(mm)', required=True) active = fields.Boolean('有效', default=True) + remark = fields.Char('描述') diff --git a/sf_base/views/base_view.xml b/sf_base/views/base_view.xml index bd6f103b..ccac31c9 100644 --- a/sf_base/views/base_view.xml +++ b/sf_base/views/base_view.xml @@ -645,6 +645,7 @@ + diff --git a/sf_manufacturing/models/product_template.py b/sf_manufacturing/models/product_template.py index 0528d65d..3554967c 100644 --- a/sf_manufacturing/models/product_template.py +++ b/sf_manufacturing/models/product_template.py @@ -173,7 +173,6 @@ class ResProductMo(models.Model): '压紧方式', domain=[('type', '=', '压紧方式')]) name = fields.Char('产品名称', compute='_compute_tool_name', store=True, required=False) - @api.constrains('seller_ids') def _check_seller_ids(self): @@ -896,8 +895,9 @@ class ResProductMo(models.Model): 'model_long': self.format_float(item['model_long'] + embryo_redundancy_id.long), 'model_width': self.format_float(item['model_width'] + embryo_redundancy_id.width), 'model_height': self.format_float(item['model_height'] + embryo_redundancy_id.height), - 'model_volume': self.format_float(item['blank_volume']), - 'model_area': self.format_float(item['blank_area']), + 'model_volume': self.format_float((item['model_long'] + embryo_redundancy_id.long) * ( + item['model_width'] + embryo_redundancy_id.width) * ( + item['model_height'] + embryo_redundancy_id.height)), 'product_model_type_id': model_type.id, 'model_processing_panel': item['processing_panel_detail'], 'model_machining_precision': item['model_machining_precision'], @@ -1114,9 +1114,9 @@ class ResProductMo(models.Model): image_data = fileObj.read() base64_data = base64.b64encode(image_data) return base64_data - + # 增加产品表面积 - + class ResProductFixture(models.Model): diff --git a/sf_manufacturing/models/purchase_order.py b/sf_manufacturing/models/purchase_order.py index 3d71b6c7..044b68e1 100644 --- a/sf_manufacturing/models/purchase_order.py +++ b/sf_manufacturing/models/purchase_order.py @@ -59,18 +59,15 @@ class PurchaseOrder(models.Model): production_id = self.env['mrp.production'].search([('origin', 'in', origins)]) purchase.production_count = len(production_id) - # def button_confirm(self): - # super().button_confirm() - # workorders = self.env['mrp.workorder'].search([('purchase_id', '=', self.id), ('state', '!=', 'cancel')]) - # for workorder in workorders: - # if workorder.routing_type == '表面工艺' and workorder.is_subcontract is True: - # move_out = workorder.move_subcontract_workorder_ids[1] - # for mo in move_out: - # if mo.state != 'done': - # mo.write({'state': 'assigned', 'production_id': False}) - # if not mo.move_line_ids: - # self.env['stock.move.line'].create(mo.get_move_line(workorder.production_id, workorder)) - # return True + def button_confirm(self): + for record in self: + for line in record.order_line: + if line.product_qty <= 0: + raise UserError('请对【产品】中的【数量】进行输入') + if line.price_unit <= 0: + raise UserError('请对【产品】中的【单价】进行输入') + return super(PurchaseOrder, self).button_confirm() + origin_sale_id = fields.Many2one('sale.order', string='销售订单号', store=True, compute='_compute_origin_sale_id') origin_sale_ids = fields.Many2many('sale.order', string='销售订单号(多个)', store=True, diff --git a/sf_manufacturing/models/stock.py b/sf_manufacturing/models/stock.py index 245c1b11..051f0507 100644 --- a/sf_manufacturing/models/stock.py +++ b/sf_manufacturing/models/stock.py @@ -564,6 +564,18 @@ class StockPicking(models.Model): sale_order_id = fields.Many2one('sale.order', '销售单号', compute='_compute_move_ids', store=True) picking_type_sequence_code = fields.Char(related='picking_type_id.sequence_code') + part_numbers = fields.Char(string="零件图号", compute='_compute_part_info', store=True, index=True) + part_names = fields.Char(string="零件名称", compute='_compute_part_info', store=True, index=True) + + @api.depends('move_ids_without_package.part_number', 'move_ids_without_package.part_name') + def _compute_part_info(self): + for picking in self: + # 聚合所有关联行的 part_number 和 part_name + part_numbers = picking.move_ids_without_package.mapped('part_number') + part_names = picking.move_ids_without_package.mapped('part_name') + picking.part_numbers = ','.join(filter(None, part_numbers)) + picking.part_names = ','.join(filter(None, part_names)) + @api.depends('move_ids', 'move_ids.product_id') def _compute_move_ids(self): for item in self: diff --git a/sf_manufacturing/views/stock_picking_view.xml b/sf_manufacturing/views/stock_picking_view.xml index 3cb877a9..f5b1941b 100644 --- a/sf_manufacturing/views/stock_picking_view.xml +++ b/sf_manufacturing/views/stock_picking_view.xml @@ -68,14 +68,8 @@ context="{'group_by': 'retrospect_ref'}"/> - - + + @@ -97,7 +91,8 @@ True - diff --git a/sf_mrs_connect/models/sync_common.py b/sf_mrs_connect/models/sync_common.py index cd0d8405..652ec3eb 100644 --- a/sf_mrs_connect/models/sync_common.py +++ b/sf_mrs_connect/models/sync_common.py @@ -3214,6 +3214,7 @@ class EmbryoRedundancySync(models.Model): embryo_redundancy.width = item['width'] embryo_redundancy.height = item['height'] embryo_redundancy.active = item['active'] + embryo_redundancy.remark = item['remark'] else: self.env['sf.embryo.redundancy'].sudo().create({ "name": item['name'], @@ -3222,4 +3223,5 @@ class EmbryoRedundancySync(models.Model): "width": item['width'], "height": item['height'], "active": item['active'], + "remark": item['remark'], }) \ No newline at end of file diff --git a/sf_quality/data/insepection_report_template.xml b/sf_quality/data/insepection_report_template.xml index f605b3e4..8362a589 100644 --- a/sf_quality/data/insepection_report_template.xml +++ b/sf_quality/data/insepection_report_template.xml @@ -16,13 +16,24 @@
- -
- 报告编号:ceshi -
-
- 扫描二维码查看PDF报告 -
+ + +
+ 报告编号: +
+
+ 扫描二维码查看PDF报告 +
+
+ + +
+ 报告编号:ceshi +
+
+ 扫描二维码查看PDF报告 +
+