ვინც Odoo-ში შეკვეთების, Helpdesk-ტიკეტებისა და დროის აღრიცხვის საფუძველზე აგზავნის ინვოისებს, იცნობს შედეგს: მომხმარებელი თვის ბოლოს ერთ ინვოისს კი არა, ხუთს იღებს. თითოეული სხვა შეკვეთიდან ან ტიკეტიდანაა, თითოეული შინაარსობრივად სწორია – და მაინც მომხმარებელი კითხულობს, ხომ არ დაეტეოდა ეს ერთ დოკუმენტში.
სწორედ ამისთვის არ არსებობს Odoo 19 Community-ში სუფთა გადაწყვეტა. ცნობილი OCA-ს მოდული account_invoice_merge Odoo 19-ისთვის ჯერ არ არის გამოშვებული და პრობლემას ისე წყვეტს, რომ ეს მომსახურების პროვაიდერებისთვის სერიოზულ რისკს წარმოადგენს: ერთნაირი პოზიციები გაერთიანდება და ამით იკარგება კავშირი უკან შეკვეთასთან, დავალებასთან და აღრიცხულ დროსთან.
ამიტომ ჩვენი საკუთარი Odoo 19 Community ინსტალაციისთვის შევქმენით მოდული Merge Draft Invoices. რადგან ეს პრობლემა მხოლოდ ჩვენ არ გვეხება, მას თავისუფალი AGPL-3 ლიცენზიით უფასოდ ვაქვეყნებთ GitHub-ზე.
პროექტზე GitHub-ზე გადასვლა:
რატომ საკუთარი მოდული OCA-ს გადაწყვეტის ნაცვლად?
OCA წლების განმავლობაში უზრუნველყოფს account_invoice_merge-ის მხარდაჭერას რეპოზიტორიაში OCA/account-invoicing. ამ პოსტის დაწერის მომენტისთვის მიგრაცია 19.0-ზე იქ ჯერ ღია Pull Request-ია და არა გამოქვეყნებული მოდული.
უფრო მნიშვნელოვანია, რომ ორი მოდული სხვადასხვა საქმეს აკეთებს:
| OCA account_invoice_merge | Merge 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-ს სტანდარტული ინვოისის ანგარიში თავისით ამატებს შუალედურ ჯამს თითო სექციაზე. ამგვარად, მომხმარებელი ხედავს, რა ღირდა თითოეული ნაწილი.
წარწერა იქმნება იმისგან, რაც წყაროს რეალურად აქვს:
- შეკვეთები, პირდაპირ პოზიციების კავშირებიდან – სახელები ამგვარად ზუსტია; თუ შეკვეთის კავშირები აკლია, გამოიყენება თავისუფალი ტექსტი invoice_origin-იდან
- ტიკეტები, დროის აღრიცხვის მეშვეობით დადგენილი – გამოდის როგორც ტიკეტი: HT00219 ან ტიკეტები: …, შეზღუდული ხუთით პლუს „და კიდევ N“
- თუ არც ერთი არ არსებობს: საწყისი ინვოისის ნომერი, შემდეგ მისი რეფერენსი
- თუ საერთოდ არაფერია: ნეიტრალური „სხვა პოზიციები“, რათა ერთი წყაროს სტრიქონები არასდროს გაუჩინარდეს უხმოდ ზემოთ მდებარე ბლოკში
რაც აკლია, გამოტოვებულია და არ გამოგონილა. ტიკეტი იბეჭდება საკუთარი ნომრით – 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-ს დამატებითი მოდულის მსგავსად:
- მოდულის დირექტორია დააკოპირეთ კონფიგურირებულ Addons-ის გზაზე.
- განაახლეთ აპლიკაციების სია Odoo-ში.
- დააინსტალირეთ მოდული 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-ისთვის.