Split 49 modules/suites into independent git repos; untrack from monorepo
Each top-level module/suite folder is now its own private repo on GitHub (gsinghpal/<name>) and gitea (admin/<name>), with a fresh single initial commit. The monorepo no longer tracks them (added to .gitignore + git rm --cached); working-tree files are retained on disk and managed in their own repos. The monorepo keeps shared root files (CLAUDE.md, docs/, scripts/, tools/, AGENTS.md, WIP/obsolete dirs) and full history. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,150 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2026 Nexa Systems Inc.
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
"""account.move overrides for Fusion Plating:
|
||||
|
||||
1. Block direct creation of out_invoice / out_refund for ALL users
|
||||
including administrators. The only legal entry points are:
|
||||
* sale.order._create_invoices() - sets context fp_from_so_invoice=True
|
||||
* manual create() with invoice_origin matching an existing sale.order.name
|
||||
|
||||
2. Once a customer move is created via a legitimate path, derive its
|
||||
name from the SO's parent number (IN-30000 / IN-30000-02 for
|
||||
invoices, CN-30000 / CN-30000-02 for credit notes). Per the
|
||||
2026-05-12 parent-number hierarchy design.
|
||||
|
||||
3. On post, link the invoice back to its fp.job's portal job (mark
|
||||
complete, stamp invoice_ref). Pre-existing behaviour, preserved.
|
||||
"""
|
||||
import logging
|
||||
|
||||
from odoo import api, models
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tools.translate import _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
CUSTOMER_TYPES = ('out_invoice', 'out_refund', 'out_receipt')
|
||||
|
||||
|
||||
class AccountMove(models.Model):
|
||||
_inherit = ['account.move', 'fp.parent.numbered.mixin']
|
||||
|
||||
# =================================================================
|
||||
# Parent-numbered mixin hooks
|
||||
# =================================================================
|
||||
def _fp_parent_sale_order(self):
|
||||
"""Find linked SO via SO context flag (set by _create_invoices),
|
||||
or fall back to invoice_origin name match, then to the reversed
|
||||
entry's SO (for the Add Credit Note path where invoice_origin
|
||||
has copy=False and doesn't survive the move.copy())."""
|
||||
so_id = self.env.context.get('fp_invoice_source_so_id')
|
||||
if so_id:
|
||||
so = self.env['sale.order'].browse(so_id).exists()
|
||||
if so:
|
||||
return so
|
||||
if self.invoice_origin:
|
||||
so = self.env['sale.order'].search(
|
||||
[('name', '=', self.invoice_origin)], limit=1,
|
||||
)
|
||||
if so:
|
||||
return so
|
||||
# Reversal path: read the parent move's SO link so the credit
|
||||
# note's name flows from the same parent number as the invoice
|
||||
# it's reversing.
|
||||
if self.reversed_entry_id:
|
||||
parent_so = self.reversed_entry_id._fp_parent_sale_order()
|
||||
if parent_so:
|
||||
return parent_so
|
||||
return self.env['sale.order']
|
||||
|
||||
def _fp_name_prefix(self):
|
||||
return 'CN' if self.move_type == 'out_refund' else 'IN'
|
||||
|
||||
def _fp_parent_counter_field(self):
|
||||
return 'x_fc_pn_cn_count' if self.move_type == 'out_refund' else 'x_fc_pn_invoice_count'
|
||||
|
||||
# =================================================================
|
||||
# Create override: block off-flow + assign parent-derived name
|
||||
# =================================================================
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for vals in vals_list:
|
||||
self._fp_validate_customer_invoice(vals)
|
||||
moves = super().create(vals_list)
|
||||
for mv in moves:
|
||||
if mv.move_type in CUSTOMER_TYPES:
|
||||
mv._fp_assign_parent_name()
|
||||
return moves
|
||||
|
||||
@api.model
|
||||
def _fp_validate_customer_invoice(self, vals):
|
||||
"""Refuse out_invoice / out_refund / out_receipt creation that
|
||||
didn't come through the SO workflow. Applies to ALL users
|
||||
including admins."""
|
||||
mtype = vals.get('move_type', 'entry')
|
||||
if mtype not in CUSTOMER_TYPES:
|
||||
return
|
||||
if self.env.context.get('fp_from_so_invoice'):
|
||||
return
|
||||
origin = (vals.get('invoice_origin') or '').strip()
|
||||
if origin and self.env['sale.order'].sudo().search_count(
|
||||
[('name', '=', origin)]
|
||||
):
|
||||
return
|
||||
# Credit-note / reversal path: Odoo's "Add Credit Note" wizard
|
||||
# calls move.copy() with reversed_entry_id set in the defaults,
|
||||
# but invoice_origin has copy=False on the standard field so
|
||||
# it doesn't survive the copy. Allow reversals through as long
|
||||
# as the reversed entry is itself a customer-facing move (which
|
||||
# means it already went through this validator at original
|
||||
# creation time - the audit trail is intact).
|
||||
reversed_id = vals.get('reversed_entry_id')
|
||||
if reversed_id:
|
||||
parent = self.env['account.move'].sudo().browse(reversed_id)
|
||||
if parent.exists() and parent.move_type in CUSTOMER_TYPES:
|
||||
return
|
||||
raise UserError(_(
|
||||
'Customer invoices, credit notes, and receipts must be '
|
||||
'created from a Sale Order. Open the originating SO and '
|
||||
'use the Create Invoice / Add Credit Note action.\n\n'
|
||||
'This rule applies to all users including administrators. '
|
||||
'It is enforced to keep the parent-number audit trail '
|
||||
'intact (see fusion_plating numbering policy).'
|
||||
))
|
||||
|
||||
# =================================================================
|
||||
# Post hook: link the invoice to its fp.job's portal job
|
||||
# =================================================================
|
||||
def action_post(self):
|
||||
result = super().action_post()
|
||||
for invoice in self.filtered(
|
||||
lambda m: m.move_type in CUSTOMER_TYPES
|
||||
):
|
||||
invoice._fp_link_to_job()
|
||||
return result
|
||||
|
||||
def _fp_link_to_job(self):
|
||||
self.ensure_one()
|
||||
if not self.invoice_origin:
|
||||
return
|
||||
Job = self.env['fp.job'].sudo()
|
||||
SO = self.env['sale.order'].sudo()
|
||||
so = SO.search([('name', '=', self.invoice_origin)], limit=1)
|
||||
if not so:
|
||||
return
|
||||
job = Job.search([('sale_order_id', '=', so.id)], limit=1)
|
||||
if not job or not job.portal_job_id:
|
||||
return
|
||||
portal = job.portal_job_id
|
||||
if 'invoice_ref' in portal._fields:
|
||||
portal.invoice_ref = self.name
|
||||
# Recompute state via the central helper - it'll only land on
|
||||
# 'complete' if the WO is actually done AND the shipment is
|
||||
# delivered. Posting an invoice early no longer skips the floor.
|
||||
if hasattr(portal, '_fp_recompute_portal_state'):
|
||||
portal._fp_recompute_portal_state()
|
||||
_logger.info(
|
||||
'Invoice %s linked to fp.job %s portal %s',
|
||||
self.name, job.name, portal.name,
|
||||
)
|
||||
Reference in New Issue
Block a user