Skip to Content

Odoo 19 Community: მიმდინარე ინვოისების გაერთიანება – ჩვენი ღია წყაროს მოდული Merge Draft Invoices

18 სექტემბერი, 2026 by
Odoo 19 Community: მიმდინარე ინვოისების გაერთიანება – ჩვენი ღია წყაროს მოდული Merge Draft Invoices
Alexander Biringer | Biricon IT Services GmbH

ვინც Odoo-ში შეკვეთების, Helpdesk-ტიკეტებისა და დროის აღრიცხვის საფუძველზე აგზავნის ინვოისებს, იცნობს შედეგს: მომხმარებელი თვის ბოლოს ერთ ინვოისს კი არა, ხუთს იღებს. თითოეული სხვა შეკვეთიდან ან ტიკეტიდანაა, თითოეული შინაარსობრივად სწორია – და მაინც მომხმარებელი კითხულობს, ხომ არ დაეტეოდა ეს ერთ დოკუმენტში.

სწორედ ამისთვის არ არსებობს Odoo 19 Community-ში სუფთა გადაწყვეტა. ცნობილი OCA-ს მოდული account_invoice_merge Odoo 19-ისთვის ჯერ არ არის გამოშვებული და პრობლემას ისე წყვეტს, რომ ეს მომსახურების პროვაიდერებისთვის სერიოზულ რისკს წარმოადგენს: ერთნაირი პოზიციები გაერთიანდება და ამით იკარგება კავშირი უკან შეკვეთასთან, დავალებასთან და აღრიცხულ დროსთან.

ამიტომ ჩვენი საკუთარი Odoo 19 Community ინსტალაციისთვის შევქმენით მოდული Merge Draft Invoices. რადგან ეს პრობლემა მხოლოდ ჩვენ არ გვეხება, მას თავისუფალი AGPL-3 ლიცენზიით უფასოდ ვაქვეყნებთ GitHub-ზე.

პროექტზე GitHub-ზე გადასვლა:

Odoo 19 Merge Draft Invoices

რატომ საკუთარი მოდული OCA-ს გადაწყვეტის ნაცვლად?

OCA წლების განმავლობაში უზრუნველყოფს account_invoice_merge-ის მხარდაჭერას რეპოზიტორიაში OCA/account-invoicing. ამ პოსტის დაწერის მომენტისთვის მიგრაცია 19.0-ზე იქ ჯერ ღია Pull Request-ია და არა გამოქვეყნებული მოდული.

უფრო მნიშვნელოვანია, რომ ორი მოდული სხვადასხვა საქმეს აკეთებს:


OCA account_invoice_mergeMerge Draft Invoices
შედეგიერწყმის არჩეულ ინვოისთაგან ერთ-ერთსყოველთვის ახალი ინვოისი
პოზიციებიერთნაირი პოზიციები გაერთიანდება, რაოდენობები ჯამდებათითოეული პოზიცია გადაიტანება ერთი ერთზე
წარმოშობაinvoice_origin / payment_reference სათაურში ერთმანეთზეა მიმაგრებულიწარმოშობის ველები თითო პოზიციაზე კოპირდება Odoo-ს საკუთარი _copy_data_extend_business_fields Hook-ით
დროის აღრიცხვაარ არის გათვალისწინებულიtimesheet_invoice_id გადაეკეთება ახალ ინვოისზე
მომხმარებლისთვის წაკითხვადიარაფერისათაური თითოეული წყაროს პოზიციების ზემოთ შეკვეთითა და ტიკეტით
საწყისი ინვოისებიუქმდებაგაუქმება (სტანდარტი), წაშლა ან შენარჩუნება – მკაფიოდ დასახელებული შედეგებით

ერთნაირი პოზიციების გაერთიანება გადამწყვეტი განსხვავებაა. გაერთიანებულ პოზიციას მხოლოდ ერთი წარმოშობა შეიძლება ჰქონდეს. როგორც კი ორი სხვადასხვა შეკვეთიდან ორი სტრიქონი ერთნაირად გამოიყურება, კავშირი ინვოისიდან უკან შეკვეთასთან, დავალებასთან და დროის ჩანაწერთან იკარგება. მომსახურების საწარმოში, სადაც ანგარიშსწორება ტიკეტი → დროის აღრიცხვა → ინვოისი ჯაჭვით მიმდინარეობს, ეს ნიშნავს: ერთი და იგივე საათები ორჯერ ითვლება ან ქრება.

ამიტომ ჩვენი მოდული პრინციპულად არაფერს აერთიანებს. გაერთიანებულ ინვოისს ზუსტად იმდენი პოზიცია აქვს, რამდენიც მისი წყაროებისას ჯამში.

OCA-ს მოდულიდან არანაირი კოდი არ გადმოგვიტანია. ორივე მოდული AGPL-3-ის ქვეშაა.

რას აკეთებს მოდული

ინვოისების სიის ხედში მონიშნეთ ორი ან მეტი მიმდინარე ინვოისი, მოქმედებების მენიუში აირჩიეთ მიმდინარე ინვოისების გაერთიანება – ასისტენტი აჩვენებს არჩევანს, შემოწმებებსა და საწყისი ინვოისების ოფციებს. დადასტურების შემდეგ პირდაპირ ახალ ინვოისზე ხვდებით.

გაერთიანებამდე მოდული ამოწმებს, ისიც ყველა პუნქტს ერთბაშად, რომ ყოველი შესწორების შემდეგ მომდევნო შეცდომა არ დაინახოთ:

  • არჩეულია მინიმუმ ორი ინვოისი
  • ყველა დრაფტის სტატუსშია
  • არც ერთი არასდროს გატარებულა (დრაფტად დაბრუნებული დოკუმენტი ინარჩუნებს გატარების ისტორიას და უარყოფილია)
  • არც ერთი არ არის უკვე გაერთიანებული
  • ყველა არის ინვოისი, მომწოდებლის ინვოისი, კრედიტ-ნოტა ან დოკუმენტი – არა წმინდა გატარების ჩანაწერები
  • ერთი და იგივე პარტნიორი, დოკუმენტის ტიპი, კომპანია, ვალუტა და ჟურნალი

განსხვავებები გადახდის პირობაში, საგადასახადო მიკუთვნებაში, მიწოდების მისამართში, საბანკო ანგარიშში ან გამყიდველში არ ბლოკავს. ასისტენტი აფრთხილებს და იღებს არჩეულთაგან ყველაზე ძველი ინვოისის მნიშვნელობას.

რა შენარჩუნდება

მოდულის არსი ისაა, რომ ყოველი კავშირი, რომელიც ინვოისის პოზიციას თავის წარმოშობასთან აქვს, ახალ ინვოისზეც არსებობს:

კავშირიდამუშავება
გაყიდვის შეკვეთის პოზიციები (sale_line_ids)თითო პოზიციაზე კოპირდება Odoo-ს _copy_data_extend_business_fields-ით
შესყიდვის შეკვეთის პოზიციები (purchase_line_id)ანალოგიურად
დანახარჯთა ცენტრების განაწილება (analytic_distribution)კოპირდება თითო პოზიციაზე
სხვა წარმოშობის ველები პოზიციის მიხედვითავტომატურად, თუ მომწოდებელი მოდული დოკუმენტირებულ Odoo Hook-ს ახორციელებს
წარმოშობა და რეფერენსი ინვოისის სათაურშიგაერთიანებულია, დუბლიკატებისგან გასუფთავებულია, ასისტენტში რედაქტირებადია
გაყიდვის შეკვეთა: invoice_ids, invoice_status, qty_invoicedგამოითვლება პოზიციებიდან – შეკვეთა ახალ ინვოისს თავისით ამოიცნობს
დროის აღრიცხვა (timesheet_invoice_id)გადაეკეთება ახალ ინვოისზე, სანამ წყაროები არავალიდური გახდება
Helpdesk-ტიკეტიუკავშირდება დროის აღრიცხვას და ამგვარად ავტომატურად ახალ ინვოისზე მიუთითებს
Chatterშეტყობინება ახალ ინვოისზე ყველა წყაროთი, შეტყობინება თითოეულ წყაროზე ახალზე ბმულით
merged_into_move_id / merged_from_move_idsშენახული კავშირი ორივე მიმართულებით

საგადასახადო სტრიქონები, გადახდის პირობების სტრიქონები, დამრგვალებისა და სკონტოს სტრიქონები არ კოპირდება, არამედ Odoo-ს მიერ შინაარსიდან თავიდან გამოითვლება. ამგვარად, გაერთიანებულ ინვოისს თითო გადასახადის განაკვეთზე ერთი საგადასახადო სტრიქონი აქვს და არა თითო წყაროზე ერთი. ის შეიქმნება დანომრვის გარეშე, როგორც დრაფტი; თავად გაერთიანება დოკუმენტის ნომერს არ ხარჯავს.

სათაურები: მომხმარებელი ხედავს, საიდან არის თითოეული პოზიცია

ეს ის ფუნქციაა, რომელიც ყოველდღიურობაში განსხვავებას ქმნის. თითოეული საწყისი ინვოისის პოზიციების ზემოთ მოდული სექციის სტრიქონს ათავსებს – იმავე ელემენტს, რომელსაც Odoo თავად იყენებს სექციისთვის „წინასწარი გადახდები“:

BE10181                                     <- სათაური
  22,500 x ტექნიკოსის საათი       3.577,50
                    შუალედური ჯამი 3.577,50
BE10182 · ტიკეტი: HT00219                   <- სათაური
   3,000 x ტექნიკოსის საათი         477,00
                    შუალედური ჯამი   477,00
                      ნეტო თანხა 4.054,50

სათაური იბეჭდება დოკუმენტზე, რომელსაც მომხმარებელი იღებს, და Odoo-ს სტანდარტული ინვოისის ანგარიში თავისით ამატებს შუალედურ ჯამს თითო სექციაზე. ამგვარად, მომხმარებელი ხედავს, რა ღირდა თითოეული ნაწილი.

წარწერა იქმნება იმისგან, რაც წყაროს რეალურად აქვს:

  1. შეკვეთები, პირდაპირ პოზიციების კავშირებიდან – სახელები ამგვარად ზუსტია; თუ შეკვეთის კავშირები აკლია, გამოიყენება თავისუფალი ტექსტი invoice_origin-იდან
  2. ტიკეტები, დროის აღრიცხვის მეშვეობით დადგენილი – გამოდის როგორც ტიკეტი: HT00219 ან ტიკეტები: …, შეზღუდული ხუთით პლუს „და კიდევ N“
  3. თუ არც ერთი არ არსებობს: საწყისი ინვოისის ნომერი, შემდეგ მისი რეფერენსი
  4. თუ საერთოდ არაფერია: ნეიტრალური „სხვა პოზიციები“, რათა ერთი წყაროს სტრიქონები არასდროს გაუჩინარდეს უხმოდ ზემოთ მდებარე ბლოკში

რაც აკლია, გამოტოვებულია და არ გამოგონილა. ტიკეტი იბეჭდება საკუთარი ნომრით – helpdesk_mgmt-ის ნომერი, Enterprise-ticket_ref ან მოდელის საჩვენებელი სახელი – არასდროს მონაცემთა ბაზის ID-ით.

სათაურები იწერება მომხმარებლის ენაზე, ისევე, როგორც Odoo აკეთებს შეკვეთებიდან ინვოისების შექმნისას. Chatter რჩება მომხმარებლის (მომხმარებლის ინტერფეისის) ენაზე.

სათაურს არ აქვს პროდუქტი, რაოდენობა, ფასი, გადასახადი, ანგარიში და დანახარჯთა ცენტრების განაწილება. ის ვერ ახდენს გავლენას ჯამებზე, საგადასახადო ბაზაზე და დანახარჯთა აღრიცხვაზე. ვინც ასისტენტში სათაურებს გამორთავს, იღებს ზუსტად იმ ინვოისს, რომელიც ამ ფუნქციის გარეშე შეიქმნებოდა – ტესტი ორივე შედეგს ცენტამდე ადარებს.

რა ემართება საწყის ინვოისებს

ასისტენტი სამ ოფციას გვთავაზობს:

  • გაუქმება (სტანდარტი, რეკომენდებული). დრაფტის გაუქმება შედეგების გარეშეა: არც დოკუმენტის ნომერი, არც გატარება. ინვოისები რჩება მონაცემთა ბაზაში გაუქმებულ დოკუმენტებად Chatter-ითა და ახალ ინვოისზე ბმულით. qty_invoiced გაყიდვის შეკვეთაზე გაუქმებულ დოკუმენტებს გამოტოვებს, არაფერი ორჯერ ითვლება. ეს არის ის ოფცია, რომელიც ექვსი თვის შემდეგაც გასაგები რჩება.
  • წაშლა. შეუქცევადად. Chatter და უკუმიმართვა ჩანაწერთან ერთად იკარგება; მხოლოდ შეტყობინება ახალ ინვოისზე რჩება. გამოსადეგია ერთჯერადი დრაფტებისთვის, რომლებიც აღარავის უნდა ენახოს.
  • დრაფტად დატოვება. წყაროები ღია რჩება. მათი გაყიდვის შეკვეთები მაშინ წყაროსაც და ახალ ინვოისსაც ინვოისირებულად ითვლიან – სამუშაო ორჯერ ანგარიშწორებულად გამოიყურება, სანამ ვინმე დრაფტებს ხელით არ გაასუფთავებს. გათვალისწინებულია იშვიათი შემთხვევისთვის, როცა გადაწყვეტილებამდე შედარება გინდათ. ტესტი სწორედ ამ ორმაგ დათვლას აფიქსირებს, რათა ის არავის გააკვირვოს.

საზღვრები – რას არ აკეთებს მოდული შეგნებულად

გთხოვთ, წაიკითხოთ სამუშაო გამოყენებამდე:

  • მხოლოდ დრაფტები. გატარებული ინვოისები არ ერწყმის. ეს კრედიტ-ნოტას საქმეა და არა კოპირების პროცესი.
  • ერთხელ გატარებული, დაბრუნებული დრაფტები უარყოფილია. მათ აქვთ ნომერი და გატარების ისტორია; ამის სხვა დოკუმენტში დამალვა ისეთი რამ არ არის, რასაც ერთობლივმა მოქმედებამ უხმოდ უნდა გაუმკლავდეს.
  • არანაირი გაერთიანება პარტნიორების, ვალუტების, კომპანიებისა და ჟურნალების საზღვრებს გარეთ. ამისთვის არ არსებობს გონივრული ერთეული მნიშვნელობა, ამიტომ მოდული უარს ამბობს და არ ხვდება.
  • payment_reference არ გადმოდის. სტრუქტურირებული გადახდის რეფერენსები ერთ დოკუმენტს ახასიათებს; ორი ერთმანეთზე მიმაგრებული ბანკში არაფერს ერგება. ახალი ინვოისი გატარებისას საკუთარს მიიღებს.
  • წინასწარი გადახდის პოზიციები კოპირდება ისევე, როგორც ნებისმიერი სხვა პოზიცია. თუ წყაროები შეიცავს sale-დან წინასწარი გადახდის სტრიქონებს, შედეგი გატარებამდე უნდა შემოწმდეს.
  • ავტომატურად მხოლოდ timesheet_invoice_id გადაეკეთება. ყველა სხვა მოდელი, რომელსაც account.move-ზე Many2one აქვს (საკუთარი ანგარიშსწორების გაშვებები, გამოწერის კავშირები, EDI-გადაცემები), კვლავ წყაროზე მიუთითებს. ამისთვის არსებობს Hook _merge_draft_relink.
  • ერთნაირი პოზიციების გაერთიანება არ ხდება – შეგნებულად. ვისაც მოკლე, მოწესრიგებული ინვოისი სურს, აქ არასწორ ადგილას არის და ამისთვის წარმოშობის კავშირებით გადაიხდის.
  • სათაურები იქმნება ერთხელ, გაერთიანებისას. ამის შემდეგ ისინი ჩვეულებრივი სექციის სტრიქონებია: რედაქტირებადი, წაშლადი და არ განახლდება, თუ შეკვეთა ან ტიკეტი მოგვიანებით გადაერქმევა.
  • სათაური ასახელებს მთელი საწყისი ინვოისის ტიკეტებს და არა ცალკეული პოზიციისას.

ინსტალაცია Odoo 19 Community-ში

ინსტალაცია ხორციელდება ჩვეულებრივი Odoo-ს დამატებითი მოდულის მსგავსად:

  1. მოდულის დირექტორია დააკოპირეთ კონფიგურირებულ Addons-ის გზაზე.
  2. განაახლეთ აპლიკაციების სია Odoo-ში.
  3. დააინსტალირეთ მოდული account_invoice_merge_draft.

მოდული მხოლოდ account-ზეა დამოკიდებული. ყველაფერი, რაც მან იცის sale-ზე, purchase-ზე, project-ზე, hr_timesheet-ზე, sale_timesheet-ზე ან Helpdesk-მოდულზე, მუშაობის დროს დგინდება – ის ინსტალირდება და მუშაობს წმინდა ანგარიშსწორების მონაცემთა ბაზაზეც.

ამის შემდეგ: ბუღალტერია → მომხმარებლები → ინვოისები (ან მომწოდებლები → ინვოისები), მონიშნეთ ორი ან მეტი დრაფტი, კბილანას მენიუ → მიმდინარე ინვოისების გაერთიანება. მოქმედება ხელმისაწვდომია ყველა მომხმარებლისთვის, ვისაც აქვს ჯგუფი ანგარიშსწორება / ინვოისირება.

ვინც მოდულის წინა ვერსიიდან განახლდება, ერთხელ უნდა განაახლოს -u account_invoice_merge_draft --i18n-overwrite-ით, რომ შეცვლილი ველების აღნიშვნები გერმანულ თარგმანში აისახოს. ახალ ინსტალაციას ეს არ ეხება.

დეველოპერებისთვის: API და Hook-ები

გაერთიანება საკუთარი კოდიდანაც შეიძლება გამოვიძახოთ:

python

merged = draft_moves.merge_draft_moves(
    extra_vals={"invoice_date": date(2026, 3, 31), "ref": "Q1"},
    source_handling="cancel",   # "delete" ან "keep"
    add_source_sections=True,   # სათაურები; სტანდარტულად True
)

merge_draft_moves აგდებს UserError-ს ყველა უარყოფის მიზეზით, თუ არჩეული ინვოისების გაერთიანება შეუძლებელია. _merge_draft_get_errors() აბრუნებს იმავე მიზეზებს სიის სახით გამონაკლისის გარეშე.

ვისაც account.move-ზე საკუთარი უკუმიმართვები აქვს, გადაფარავს _merge_draft_relink-ს:

python

class AccountMove(models.Model):
    _inherit = "account.move"

    def _merge_draft_relink(self, new_move):
        super()._merge_draft_relink(new_move)
        if "my_move_id" not in self.env["my.model"]._fields:
            return
        self.env["my.model"].search([("my_move_id", "in", self.ids)]).write(
            {"my_move_id": new_move.id}
        )

ვისაც უკუმიმართვა ინვოისის პოზიციაზე აქვს, ამ მოდული საერთოდ არ სჭირდება: Odoo-ს საკუთარი Hook account.move.line._copy_data_extend_business_fields საკმარისია და ველი გაერთიანებისას, გაუქმებისას და დუბლირებისას თანაბრად გადადის. სხვა Helpdesk-მოდულებისთვის ან სათაურების განსხვავებული ფორმულირებისთვის არსებობს _merge_draft_get_source_tickets() და _merge_draft_section_name().

გამოცდილია სამუშაო მონაცემებზე გაშვებამდე

48 ტესტი მოიცავს შერეულ სტატუსებს, განსხვავებულ პარტნიორებს, ვალუტებსა და დოკუმენტის ტიპებს, პოზიციებს გაყიდვის ან შესყიდვის შეკვეთით და მის გარეშე, გადასახადებს, სექციებსა და შენიშვნებს, დანახარჯთა ცენტრების განაწილებას, დროის აღრიცხვის გადაკეთებას, საწყისი ინვოისების სამივე ოფციას, ასისტენტსა და სათაურებს – რომ ისინი სტანდარტულად აქტიურია, რომ მათი გამორთვა ძველ შედეგს ცენტამდე იმეორებს, რომ სათაური არ შეიცავს თანხებსა და ანგარიშებს, რომ ერთი წყაროს პოზიციები ერთად რჩება, რომ ტიკეტი თავისი ნომრით ჩანს და არასდროს მონაცემთა ბაზის ID-ით.

სატესტო მონაცემები ეფუძნება აქტიური კომპანიის არსებულ ანგარიშთა გეგმას და არა AccountTestInvoicingCommon-ს – ეს Helper მიგრირებულ სამუშაო მონაცემთა ბაზებზე ხშირად არ იწყება, სწორედ ასეთებზე უნდა მუშაობდეს კი მოდული.

odoo-bin -d <db> -u account_invoice_merge_draft --test-enable \
         --test-tags /account_invoice_merge_draft --stop-after-init

როგორც ბუღალტერიის სფეროში ნებისმიერი გაფართოების შემთხვევაში, გირჩევთ ინსტალაცია ჯერ სატესტო გარემოში საკუთარი მონაცემთა ბაზის ასლით შეამოწმოთ.

უფასოდ და ღია წყაროთი Odoo Community-ისთვის

ჩვენ Biricon IT Services GmbH-ში გადაწყვეტებს ჯერ საკუთარი საქმიანობიდან გამომდინარე კონკრეტული მოთხოვნებისთვის ვქმნით. თუ აქედან ზოგადად გამოსადეგი მოდული იქმნება, არ გვინდა, რომ ის ერთ ინსტალაციაში დავმალოთ.

ამიტომ Merge Draft Invoices უფასოდ არის ხელმისაწვდომი ღია წყაროს პროექტის სახით. წყაროს კოდის დათვალიერება, შემოწმება, ადაპტირება და AGPL-3 ლიცენზიის ფარგლებში შემდგომი განვითარება შეიძლება.

ჩვენი მიზანი იყო არა რაც შეიძლება მოკლე ინვოისი, არამედ ინვოისი, რომლის ყოველი პოზიცია ერთი წლის შემდეგაც თავის შეკვეთამდე, ტიკეტამდე და დროის ჩანაწერამდე მიგვიყვანს.

პროექტი, წყაროს კოდი და თანამშრომლობა

სრული წყაროს კოდი, ინსტალაციის მითითებები და ტექნიკური დოკუმენტაცია ხელმისაწვდომია GitHub-ზე:

Odoo 19 Merge Draft Invoices-ის გახსნა GitHub-ზე

გვიხარია პრაქტიკული გამოცდილება, შეცდომების მოხსენებები, გაუმჯობესების წინადადებები და Odoo Community-ის წვლილი.

უფასოდ. გასაგებად. ღია წყაროთი. Odoo 19 Community-ისთვის.